Skip to content

Commit

Permalink
Merge pull request #4476 from chdoc/fix-cursecheck
Browse files Browse the repository at this point in the history
[cursecheck] don't try to rely on cursor; check for selected unit.
  • Loading branch information
myk002 authored Apr 21, 2024
2 parents 07274e8 + 9c842c6 commit 38b1d83
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 107 deletions.
1 change: 1 addition & 0 deletions docs/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Template for new versions:

## Fixes
- Fixed misidentification of visitors from your own civ as residents; affects all tools that iterate through citizens/residents
- `cursecheck`: don't try to rely on cursor; check for selected unit instead

## Misc Improvements
- `suspendmanager`: Account for walls planned on the z-layer below when determining accessibility to a job
Expand Down
8 changes: 4 additions & 4 deletions docs/plugins/cursecheck.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ cursecheck
:summary: Check for cursed creatures.
:tags: fort armok inspection units

This command checks a single map tile (or the whole map/world) for cursed
creatures (ghosts, vampires, necromancers, werebeasts, zombies, etc.).
This command checks a single unit or the whole map for curses (ghosts, vampires,
necromancers, werebeasts, zombies, etc.).

With an active in-game cursor, only the selected tile will be checked. Without a
cursor, the whole map will be checked.
If a unit is selected, only the selected unit will be checked. Otherwise, all
units on the map will be checked.

By default, you will just see the count of cursed creatures in case you just
want to find out if you have any of them running around in your fort. Dead and
Expand Down
139 changes: 36 additions & 103 deletions plugins/cursecheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,23 @@
// 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 "Core.h"
#include "MiscUtils.h"
#include "PluginManager.h"
#include "modules/Units.h"
#include <modules/Translation.h>

#include "modules/Gui.h"
#include "MiscUtils.h"
#include "modules/Translation.h"
#include "modules/Units.h"

#include "df/incident.h"
#include "df/syndrome.h"
#include "df/unit.h"
#include "df/unit_soul.h"
#include "df/unit_syndrome.h"
#include "df/historical_entity.h"
#include "df/historical_figure.h"
#include "df/historical_figure_info.h"
#include "df/identity.h"
#include "df/language_name.h"
#include "df/syndrome.h"
#include "df/world.h"
#include "df/world_raws.h"
#include "df/incident.h"

using std::vector;
using std::string;
Expand All @@ -55,8 +42,8 @@ DFHACK_PLUGIN("cursecheck");
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(cursor);

enum class curses : int8_t {
None,
enum curses {
None = 0,
Unknown,
Ghost,
Zombie,
Expand Down Expand Up @@ -121,12 +108,11 @@ curses determineCurse(df::unit * unit)

// werecreatures: subjected to a were syndrome. The curse effects are active only when
// in were form.
for (size_t i = 0; i < unit->syndromes.active.size(); i++)
{
for (size_t k = 0; k < world->raws.syndromes.all[unit->syndromes.active[i]->type]->syn_class.size(); k++)
{
if (strcmp (world->raws.syndromes.all[unit->syndromes.active[i]->type]->syn_class[k]->c_str(), "WERECURSE") == 0)
{
for (auto active_syndrome : unit->syndromes.active) {
auto syndrome = df::syndrome::find(active_syndrome->type);
if (syndrome) {
for (auto classname : syndrome->syn_class)
if (classname && *classname == "WERECURSE") {
cursetype = curses::Werebeast;
break;
}
Expand All @@ -143,8 +129,7 @@ curses determineCurse(df::unit * unit)
command_result cursecheck (color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;
int32_t cursorX, cursorY, cursorZ;
Gui::getCursorCoords(cursorX,cursorY,cursorZ);
df::unit* selected_unit = Gui::getSelectedUnit(out, true);

bool giveDetails = false;
bool giveUnitID = false;
Expand All @@ -153,65 +138,36 @@ command_result cursecheck (color_ostream &out, vector <string> & parameters)
bool verbose = false;
size_t cursecount = 0;

for(size_t i = 0; i < parameters.size();i++)
for(auto parameter : parameters)
{
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"
" ids - add creature and race IDs to be show on the output\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")
if(parameter == "help" || parameter == "?")
return CR_WRONG_USAGE;
if(parameter == "detail")
giveDetails = true;
if(parameters[i] == "ids")
if(parameter == "ids")
giveUnitID = true;
if(parameters[i] == "nick")
if(parameter == "nick")
giveNick = true;
if(parameters[i] == "all")
if(parameter == "all")
ignoreDead = false;
if(parameters[i] == "verbose")
if(parameter == "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)
// check whole map if no unit is selected
for(df::unit *unit : selected_unit ? vector{ selected_unit } : world->units.all)
{
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];

// filter out all "living" units that are currently removed from play
// don't spam all completely dead creatures if not explicitly wanted
if((!Units::isActive(unit) && !Units::isKilled(unit)) || (Units::isKilled(unit) && 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;
}

curses cursetype = determineCurse(unit);

if (cursetype != curses::None)
Expand All @@ -220,53 +176,30 @@ command_result cursecheck (color_ostream &out, vector <string> & parameters)

if(giveNick)
{
Units::setNickname(unit, curse_names[static_cast<size_t>(cursetype)].c_str()); //"CURSED");
Units::setNickname(unit, curse_names[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 ");
}
out << (Units::getReadableName(unit));

auto death = df::incident::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",
out.print(", born in %d, cursed in %d to be a %s. (%s%s)\n",
unit->birth_year,
unit->curse_year,
curse_names [static_cast<size_t>(cursetype)].c_str(),
curse_names[cursetype].c_str(),
// technically most cursed creatures are undead,
// therefore output 'active' if they are not completely dead
unit->flags2.bits.killed ? "deceased" : "active",
unit->flags3.bits.ghostly ? "-ghostly" : "",
missing ? "-missing" : ""
death && !death->flags.bits.discovered ? "-missing" : ""
);

// dump all curse flags on demand
if (verbose)
{
out << "Curse flags: "
<< bitfield_to_string(unit->curse.add_tags1) << endl
<< bitfield_to_string(unit->curse.add_tags2) << endl;
<< bitfield_to_string(unit->curse.add_tags1) << std::endl
<< bitfield_to_string(unit->curse.add_tags2) << std::endl;
}
}

Expand All @@ -277,10 +210,10 @@ command_result cursecheck (color_ostream &out, vector <string> & parameters)
}
}

if (checkWholeMap)
out.print("Number of cursed creatures on map: %zd \n", cursecount);
else
out.print("Number of cursed creatures on tile: %zd \n", cursecount);
if (selected_unit && !giveDetails)
out.print("Selected unit is %scursed\n", cursecount == 0 ? "not " : "");
else if (!selected_unit)
out.print("%zd cursed creatures on map\n", cursecount);

return CR_OK;
}

0 comments on commit 38b1d83

Please sign in to comment.