vampcheck is now generic cursecheck, added tweak clear-ghostly

develop
Robert Heinrich 2012-03-22 16:30:15 +01:00
parent cf029e0a2e
commit aa807343cc
7 changed files with 431 additions and 227 deletions

@ -536,21 +536,42 @@ Contains various tweaks for minor bugs (currently just one).
Options
-------
:tweak clear-missing: Remove the missing status from the selected unit. This allows engraving slabs for ghostly, but not yet found, creatures.
:tweak clear-ghostly: Remove the ghostly status from the selected unit and mark it as dead. This allows getting rid of bugged ghosts which do not show up in the engraving slab menu at all, even after using clear-missing. It works, but is potentially very dangerous - so use with care. Probably (almost certainly) it does not have the same effects like a proper burial. You've been warned.
tubefill
========
Fills all the adamantine veins again. Veins that were empty will be filled in too, but might still trigger a demon invasion (this is a known bug).
vampcheck
=========
Checks a single map tile or the whole map/world for cursed creatures (vampires).
cursecheck
==========
Checks a single map tile or the whole map/world for cursed creatures (ghosts, vampires, necromancers, werebeasts, zombies).
With an active in-game cursor only the selected tile will be observed. Without a cursor the whole map will be checked.
By default vampires will be only counted in case you just want to find out if you have any of them running around in your fort.
By default cursed creatures will be only counted in case you just want to find out if you have any of them running around in your fort.
By default dead and passive creatures (ghosts who were put to rest, killed vampires, ...) are ignored.
Undead skeletons, corpses, bodyparts and the like are all thrown into the curse category "zombie".
Anonymous zombies and resurrected body parts will show as "unnamed creature".
Options
-------
:detail: Print full name, date of birth, date of curse (some vampires might use fake identities in-game, though).
:nick: Set nickname to 'CURSED' (does not always show up in-game, some vamps don't like nicknames).
:detail: Print full name, date of birth, date of curse and some status info (some vampires might use fake identities in-game, though).
:nick: Set the type of curse as nickname (does not always show up in-game, some vamps don't like nicknames).
:all: Include dead and passive cursed creatures (can result in a quite long list after having FUN with necromancers).
:verbose: Print all curse tags (if you really want to know it all).
Examples:
---------
Check one single map tile if one of the creatures on it is cursed (in-game cursor required):
* cursecheck
Count all active cursed creatures who roam around on your map (no in-game cursor) without giving more details:
* cursecheck
Give detailed info about all cursed creatures including deceased ones (no in-game cursor):
* cursecheck detail all
Give a nickname all living/active cursed creatures on the map(no in-game cursor):
* cursecheck nick
.. note::
* If you do a full search (with the option "all") former ghosts will show up with the cursetype "unknown" because their ghostly flag is not set anymore. But if you happen to find a living/active creature with cursetype "unknown" please report that in the dfhack thread on the modding forum or per irc. This is likely to happen with mods which introduce new types of curses, for example.
vdig
====

@ -109,10 +109,10 @@ 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)
# this is a plugin which helps detect cursed creatures (vampires, necromancers, werebeasts, ...)
OPTION(BUILD_CURSECHECK "Build the cursecheck plugin." ON)
if(BUILD_CURSECHECK)
add_subdirectory(cursecheck)
endif()
# alternative version of liquids which can be used non-interactively after configuring it

@ -1,11 +1,11 @@
PROJECT (vampcheck)
PROJECT (cursecheck)
# A list of source files
SET(PROJECT_SRCS
vampcheck.cpp
cursecheck.cpp
)
# A list of headers
SET(PROJECT_HDRS
vampcheck.h
cursecheck.h
)
SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE)
@ -30,4 +30,4 @@ ELSE(UNIX)
)
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})
DFHACK_PLUGIN(cursecheck ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS})

@ -0,0 +1,372 @@
// cursecheck plugin
//
// check single tile or whole map/world for cursed creatures by checking if a valid curse date (!=-1) is set
// if a cursor is active only the selected tile will be observed
// without cursor the whole map will be checked
// by default cursed creatures will be only counted
//
// the tool was intended to help finding vampires but it will also list necromancers, werebeasts and zombies
//
// Options:
// detail - print full name, date of birth, date of curse (vamp might use fake identity, though)
// show if the creature is active or dead, missing, ghostly (ghost vampires should not exist in 34.05)
// identify type of curse (works fine for vanilla ghosts, vamps, werebeasts, zombies and necromancers)
// nick - set nickname to 'CURSED' (does not always show up ingame, some vamps don't like nicknames)
// all - don't ignore dead and inactive creatures (former ghosts, dead necromancers, ...)
// verbose - acts like detail but also lists all curse tags (if you want to know it all).
#include <iostream>
#include <iomanip>
#include <climits>
#include <vector>
#include <string>
#include <sstream>
#include <ctime>
#include <cstdio>
using namespace std;
#include "Core.h"
#include "Console.h"
#include "PluginManager.h"
#include "modules/Units.h"
#include <modules/Translation.h>
#include "modules/Gui.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/death_info.h"
using std::vector;
using std::string;
using namespace DFHack;
using namespace df::enums;
using df::global::world;
using df::global::cursor;
command_result cursecheck (color_ostream &out, vector <string> & parameters);
DFHACK_PLUGIN("cursecheck");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand("cursecheck",
"Checks for cursed creatures (vampires, necromancers, zombies, ...).",
cursecheck, false ));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
// code for setting nicknames is copypasta 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);
}
}
}
}
void cursedump (color_ostream &out, df::unit * unit);
std::string determineCurse(df::unit * unit)
{
string cursetype = "unknown";
// ghosts: ghostly, duh
if(unit->flags3.bits.ghostly)
cursetype = "ghost";
// zombies: undead or hate life (according to ag), not bloodsuckers
if( (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE || unit->curse.add_tags1.bits.NOT_LIVING)
&& !unit->curse.add_tags1.bits.BLOODSUCKER )
cursetype = "zombie";
// necromancers: alive, don't eat, don't drink, don't age
if(!unit->curse.add_tags1.bits.NOT_LIVING
&& unit->curse.add_tags1.bits.NO_EAT
&& unit->curse.add_tags1.bits.NO_DRINK
&& unit->curse.add_tags2.bits.NO_AGING
)
cursetype = "necromancer";
// werecreatures: alive, DO eat, DO drink, don't age
if(!unit->curse.add_tags1.bits.NOT_LIVING
&& !unit->curse.add_tags1.bits.NO_EAT
&& !unit->curse.add_tags1.bits.NO_DRINK
&& unit->curse.add_tags2.bits.NO_AGING )
cursetype = "werebeast";
// vampires: bloodsucker (obvious enough)
if(unit->curse.add_tags1.bits.BLOODSUCKER)
cursetype = "vampire";
return cursetype;
}
command_result cursecheck (color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;
int32_t cursorX, cursorY, cursorZ;
Gui::getCursorCoords(cursorX,cursorY,cursorZ);
bool giveDetails = false;
bool giveNick = false;
bool ignoreDead = true;
bool verbose = false;
size_t cursecount = 0;
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters[i] == "help" || parameters[i] == "?")
{
out.print( " Search for cursed creatures (ghosts, vampires, necromancers, zombies, werebeasts).\n"
" With map cursor active only the current tile will be checked.\n"
" Without an in-game cursor the whole map/world will be scanned.\n"
" By default cursed creatures are only counted to make it more interesting.\n"
" By default dead and passive creatures (aka really dead) are ignored.\n"
"Options:\n"
" detail - show details (name and age shown ingame might differ)\n"
" nick - try to set cursetype as nickname (does not always work)\n"
" all - include dead and passive creatures\n"
" verbose - show all curse tags (if you really want to know it all)\n"
);
return CR_OK;
}
if(parameters[i] == "detail")
giveDetails = true;
if(parameters[i] == "nick")
giveNick = true;
if(parameters[i] == "all")
ignoreDead = false;
if(parameters[i] == "verbose")
{
// verbose makes no sense without enabling details
giveDetails = true;
verbose = 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];
// don't spam all completely dead creatures if not explicitly wanted
if(unit->flags1.bits.dead && ignoreDead)
{
continue;
}
// 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-cursed creatures have curse_year == -1
if(unit->relations.curse_year != -1)
{
cursecount++;
string cursetype = determineCurse(unit);
if(giveNick)
{
setUnitNickname(unit, cursetype); //"CURSED");
}
if(giveDetails)
{
if(unit->name.has_name)
{
string firstname = unit->name.first_name;
string restofname = Translation::TranslateName(&unit->name, false);
firstname[0] = toupper(firstname[0]);
// if creature has no nickname, restofname will already contain firstname
// no need for double output
if(restofname.compare(0, firstname.length(),firstname) != 0)
out.print("%s ", firstname.c_str());
out.print("%s ", restofname.c_str());
}
else
{
// happens with unnamed zombies and resurrected body parts
out.print("Unnamed creature ");
}
auto death = df::death_info::find(unit->counters.death_id);
bool missing = false;
if (death && !death->flags.bits.discovered)
{
missing = true;
}
out.print("born in %d, cursed in %d to be a %s. (%s%s%s)\n",
unit->relations.birth_year,
unit->relations.curse_year,
cursetype.c_str(),
// technically most cursed creatures are undead,
// therefore output 'active' if they are not completely dead
unit->flags1.bits.dead ? "deceased" : "active",
unit->flags3.bits.ghostly ? "-ghostly" : "",
missing ? "-missing" : ""
);
if (missing)
{
out.print("- You can use 'tweak clear-missing' to allow engraving a memorial easier.\n");
}
// dump all curse flags on demand
if (verbose)
{
cursedump(out, unit);
}
}
}
}
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;
}
void cursedump (color_ostream &out, df::unit * unit)
{
out << "Curse flags: ";
if(unit->curse.add_tags1.bits.BLOODSUCKER)
out << "bloodsucker ";
if(unit->curse.add_tags1.bits.EXTRAVISION)
out << "extravision ";
if(unit->curse.add_tags1.bits.OPPOSED_TO_LIFE)
out << "opposed_to_life ";
if(unit->curse.add_tags1.bits.NOT_LIVING)
out << "not_living ";
if(unit->curse.add_tags1.bits.NOEXERT)
out << "noexpert ";
if(unit->curse.add_tags1.bits.NOPAIN)
out << "nopain ";
if(unit->curse.add_tags1.bits.NOBREATHE)
out << "nobreathe ";
if(unit->curse.add_tags1.bits.HAS_BLOOD)
out << "has_blood ";
if(unit->curse.add_tags1.bits.NOSTUN)
out << "nostun ";
if(unit->curse.add_tags1.bits.NONAUSEA)
out << "nonausea ";
if(unit->curse.add_tags1.bits.NO_DIZZINESS)
out << "no_dizziness ";
if(unit->curse.add_tags1.bits.TRANCES)
out << "trances ";
if(unit->curse.add_tags1.bits.NOEMOTION)
out << "noemotion ";
if(unit->curse.add_tags1.bits.PARALYZEIMMUNE)
out << "paralyzeimmune ";
if(unit->curse.add_tags1.bits.NOFEAR)
out << "nofear ";
if(unit->curse.add_tags1.bits.NO_EAT)
out << "no_eat ";
if(unit->curse.add_tags1.bits.NO_DRINK)
out << "no_drink ";
if(unit->curse.add_tags1.bits.MISCHIEVOUS)
out << "mischievous ";
if(unit->curse.add_tags1.bits.NO_PHYS_ATT_GAIN)
out << "no_phys_att_gain ";
if(unit->curse.add_tags1.bits.NO_PHYS_ATT_RUST)
out << "no_phys_att_rust ";
if(unit->curse.add_tags1.bits.NOTHOUGHT)
out << "nothought ";
if(unit->curse.add_tags1.bits.NO_THOUGHT_CENTER_FOR_MOVEMENT)
out << "no_thought_center_for_movement ";
if(unit->curse.add_tags1.bits.CAN_SPEAK)
out << "can_speak ";
if(unit->curse.add_tags1.bits.CAN_LEARN)
out << "can_learn ";
if(unit->curse.add_tags1.bits.CRAZED)
out << "crazed ";
if(unit->curse.add_tags1.bits.BLOODSUCKER)
out << "bloodsucker ";
if(unit->curse.add_tags1.bits.SUPERNATURAL)
out << "supernatural ";
if(unit->curse.add_tags2.bits.NO_AGING)
out << "no_aging ";
if(unit->curse.add_tags2.bits.STERILE)
out << "sterile ";
if(unit->curse.add_tags2.bits.FIT_FOR_ANIMATION)
out << "fit_for_animation ";
if(unit->curse.add_tags2.bits.FIT_FOR_RESURRECTION)
out << "fit_for_resurrection ";
out << endl << endl;
}

@ -46,6 +46,11 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
"tweak", "Various tweaks for minor bugs.", tweak, false,
" tweak clear-missing\n"
" Remove the missing status from the selected unit.\n"
" tweak clear-ghostly\n"
" Remove the ghostly status from the selected unit.\n"
" Intended to fix the case where you can't engrave memorials for ghosts.\n"
" Note that this is very dirty and possibly dangerous!\n"
" Most probably does not have the positive effect of a proper burial.\n"
));
return CR_OK;
}
@ -83,6 +88,25 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
crime->flags.bits.discovered = true;
}
}
else if (cmd == "clear-ghostly")
{
df::unit *unit = getSelectedUnit(out);
if (!unit)
return CR_FAILURE;
// don't accidentally kill non-ghosts!
if (unit->flags3.bits.ghostly)
{
// remove ghostly, set to dead instead
unit->flags3.bits.ghostly = 0;
unit->flags1.bits.dead = 1;
}
else
{
out.print("That's not a ghost!\n");
return CR_FAILURE;
}
}
else return CR_WRONG_USAGE;
return CR_OK;

@ -1,213 +0,0 @@
// vampcheck plugin
//
// check single tile or whole map/world for vampires by checking if a valid curse date (!=-1) is set
// if a cursor is active only the selected tile will be observed
// without cursor the whole map will be checked
// by default vampires will be only counted
//
// Options:
// detail - print full name, date of birth, date of curse (vamp might use fake identity, though)
// nick - set nickname to 'CURSED' (does not always show up ingame, some vamps don't like nicknames)
#include <iostream>
#include <iomanip>
#include <climits>
#include <vector>
#include <string>
#include <sstream>
#include <ctime>
#include <cstdio>
using namespace std;
#include "Core.h"
#include "Console.h"
#include "PluginManager.h"
#include "modules/Units.h"
#include <modules/Translation.h>
#include "modules/Gui.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"
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 <string> & parameters);
DFHACK_PLUGIN("vampcheck");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &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 <string> & 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 (name and age shown ingame might differ)\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)
{
string firstname = unit->name.first_name;
string restofname = Translation::TranslateName(&unit->name, false);
firstname[0] = toupper(firstname[0]);
// if creature has no nickname, restofname will already contain firstname
// no need for double output
if(restofname.compare(0, firstname.length(),firstname) != 0)
out.print("%s ", firstname.c_str());
out.print("%s ", restofname.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;
}