diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 1574b5a28..bec463220 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -108,3 +108,9 @@ OPTION(BUILD_SKELETON "Build the skeleton plugin." OFF) if(BUILD_SKELETON) add_subdirectory(skeleton) endif() + +# this is a plugin which helps detect vampires +OPTION(BUILD_VAMPCHECK "Build the vampcheck plugin." ON) +if(BUILD_VAMPCHECK) + add_subdirectory(vampcheck) +endif() diff --git a/plugins/vampcheck/CMakeLists.txt b/plugins/vampcheck/CMakeLists.txt new file mode 100644 index 000000000..9b0811c1a --- /dev/null +++ b/plugins/vampcheck/CMakeLists.txt @@ -0,0 +1,33 @@ +PROJECT (vampcheck) +# A list of source files +SET(PROJECT_SRCS + vampcheck.cpp +) +# A list of headers +SET(PROJECT_HDRS + vampcheck.h +) +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) + +# mash them together (headers are marked as headers and nothing will try to compile them) +LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) + +# option to use a thread for no particular reason +#OPTION(SKELETON_THREAD "Use threads in the skeleton plugin." ON) +#linux +IF(UNIX) + add_definitions(-DLINUX_BUILD) + SET(PROJECT_LIBS + # add any extra linux libs here + ${PROJECT_LIBS} + ) +# windows +ELSE(UNIX) + SET(PROJECT_LIBS + # add any extra linux libs here + ${PROJECT_LIBS} + $(NOINHERIT) + ) +ENDIF(UNIX) +# this makes sure all the stuff is put in proper places and linked to dfhack +DFHACK_PLUGIN(vampcheck ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) diff --git a/plugins/vampcheck/vampcheck.cpp b/plugins/vampcheck/vampcheck.cpp new file mode 100644 index 000000000..7800b6cd1 --- /dev/null +++ b/plugins/vampcheck/vampcheck.cpp @@ -0,0 +1,218 @@ +// check single tile or whole map/world for vampires +// only tested in fortress mode, maybe it should bail out if called in another mode +// TODO: add some check for deceased vampires, currently lists everybody who has a curse active +// curse probably persists after death to be evauluated for legends... + +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +// TODO: check if all these includes are really necessary +// (they were thrown together from the other plugins where the code was borrowed from) + +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" +#include "modules/Units.h" +#include +#include "modules/Maps.h" +#include "modules/Gui.h" +#include "modules/Materials.h" +#include "modules/MapCache.h" +#include "modules/Buildings.h" +#include "MiscUtils.h" + +#include "df/unit.h" +#include "df/unit_soul.h" +#include "df/historical_entity.h" +#include "df/historical_figure.h" +#include "df/historical_figure_info.h" +#include "df/assumed_identity.h" +#include "df/language_name.h" + +#include "df/world.h" +#include "df/world_raws.h" +#include "df/building_def.h" + +using std::vector; +using std::string; +using namespace DFHack; +using namespace df::enums; +using df::global::world; +using df::global::cursor; + +command_result vampcheck (color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("vampcheck"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.clear(); + commands.push_back(PluginCommand("vampcheck", + "Checks for curses (vampires).", + vampcheck, false )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + + +// code for setting nicknames is borrowed from rename.cpp +// will not work in all cases, some vampires don't like to get nicks + +static void set_nickname(df::language_name *name, std::string nick) +{ + if (!name->has_name) + { + *name = df::language_name(); + + name->language = 0; + name->has_name = true; + } + + name->nickname = nick; +} + +void setUnitNickname(df::unit *unit, const std::string &nick) +{ + // There are >=3 copies of the name, and the one + // in the unit is not the authoritative one. + // This is the reason why military units often + // lose nicknames set from Dwarf Therapist. + set_nickname(&unit->name, nick); + + if (unit->status.current_soul) + set_nickname(&unit->status.current_soul->name, nick); + + df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); + if (figure) + { + set_nickname(&figure->name, nick); + + // v0.34.01: added the vampire's assumed identity + if (figure->info && figure->info->reputation) + { + auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity); + + if (identity) + { + auto id_hfig = df::historical_figure::find(identity->histfig_id); + + if (id_hfig) + { + // Even DF doesn't do this bit, because it's apparently + // only used for demons masquerading as gods, so you + // can't ever change their nickname in-game. + set_nickname(&id_hfig->name, nick); + } + else + set_nickname(&identity->name, nick); + } + } + } +} + +command_result vampcheck (color_ostream &out, vector & parameters) +{ + CoreSuspender suspend; + int32_t cursorX, cursorY, cursorZ; + Gui::getCursorCoords(cursorX,cursorY,cursorZ); + + // check command options + // by default cursed creatures are only counted + bool giveDetails = false; + bool giveNick = false; + size_t cursecount = 0; + + for(size_t i = 0; i < parameters.size();i++) + { + if(parameters[i] == "help" || parameters[i] == "?") + { + out.print( " Search for vampires. With map cursor active only the current tile\n" + " will be checked. Without cursor the whole map/world will be scanned.\n" + " By default cursed creatures are only counted to make it more interesting.\n" + "Options:\n" + " detail - show details (might give false names and ages)\n" + " nick - try to set nickname to CURSED (does not always work)\n" + ); + return CR_OK; + } + if(parameters[i] == "detail") + giveDetails = true; + if(parameters[i] == "nick") + giveNick = true; + } + + // check whole map if no cursor is active + bool checkWholeMap = false; + if(cursorX == -30000) + { + out.print("No cursor; will check all units on the map.\n"); + checkWholeMap = true; + } + + for(size_t i = 0; i < world->units.all.size(); i++) + { + df::unit * unit = world->units.all[i]; + + // bail out if we have a map cursor and creature is not at that specific position + if ( !checkWholeMap && (unit->pos.x != cursorX || unit->pos.y != cursorY || unit->pos.z != cursorZ) ) + { + continue; + } + + // non-vampires have curse_year == -1 + if(unit->relations.curse_year != -1) + { + cursecount++; + + if(giveDetails) + { + if(unit->name.has_name) + { + // TODO: avoid printing the first name twice + // currently it behaves like this (copied from dwarfexport): + // firstname nickname restofname(s) - if dwarf has nickname (fine) + // firstname firstname restofname(s) - if dwarf has no nickname (ugly) + // (so better use two strings and check if firstname occurs twice) + string info = unit->name.first_name; + info += " "; + info += Translation::TranslateName(&unit->name, false); + info[0] = toupper(info[0]); + out.print("%s ", info.c_str()); + } + else + { + // can that even happen? well, we have unnamed slabs, so maybe it can + out.print("Unnamed creature "); + } + + out.print("born in %d, cursed in %d. (%s)\n", + unit->relations.birth_year, + unit->relations.curse_year, + unit->counters.death_id == -1 ? "alive" : "deceased" + ); + } + + if(giveNick) + setUnitNickname(unit, "CURSED"); + } + } + + if (checkWholeMap) + out.print("Number of cursed creatures on map: %d \n", cursecount); + else + out.print("Number of cursed creatures on tile: %d \n", cursecount); + + return CR_OK; +} diff --git a/plugins/vampcheck/vampcheck.h b/plugins/vampcheck/vampcheck.h new file mode 100644 index 000000000..7b9637ef9 --- /dev/null +++ b/plugins/vampcheck/vampcheck.h @@ -0,0 +1 @@ +#pragma once \ No newline at end of file