New plugin: fix-unit-occupancy (fixes bug 3499)

develop
lethosor 2015-08-07 14:33:38 -04:00
parent f46d3d137f
commit bce9d98633
4 changed files with 254 additions and 0 deletions

@ -17,6 +17,7 @@ DFHack Future
kill-lua: Interrupt running Lua scripts
New plugins
confirm: Adds confirmation dialogs for several potentially dangerous actions
fix-unit-occupancy: Fixes issues with unit occupancy, such as faulty "unit blocking tile" messages (bug 3499)
New scripts
burial: sets all unowned coffins to allow burial ("-pets" to allow pets too)
fix-ster: changes fertility/sterility of animals or dwarves

@ -1264,6 +1264,22 @@ This command adds the Guild Representative position to all Human civilizations,
allowing them to make trade agreements (just as they did back in 0.28.181.40d
and earlier) in case you haven't already modified your raws accordingly.
fix-unit-occupancy
------------------
This plugin fixes issues with unit occupancy, notably issues with phantom
"unit blocking tile" messages (`Bug 3499`_). It can be run manually, or
periodically when enabled with the built-in enable/disable commands:
* ``fix-unit-occupancy``: Run the plugin immediately. Available options:
* ``-h``, ``here``, ``cursor``: Only operate on the tile at the cursor
* ``-n``, ``dry``, ``dry-run``: Do not write changes to map
* ``fix-unit-occupancy interval X``: Run the plugin every ``X`` ticks (when enabled).
The default is 1200 ticks, or 1 day. Ticks are only counted when the game is unpaused.
.. _`Bug 3499`: http://bay12games.com/dwarves/mantisbt/view.php?id=3499
fixveins
--------
Removes invalid references to mineral inclusions and restores missing ones.

@ -121,6 +121,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(filltraffic filltraffic.cpp)
DFHACK_PLUGIN(fix-armory fix-armory.cpp)
DFHACK_PLUGIN(fixpositions fixpositions.cpp)
DFHACK_PLUGIN(fix-unit-occupancy fix-unit-occupancy.cpp)
DFHACK_PLUGIN(fixveins fixveins.cpp)
DFHACK_PLUGIN(flows flows.cpp)
DFHACK_PLUGIN(follow follow.cpp)

@ -0,0 +1,236 @@
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Units.h"
#include "modules/Translation.h"
#include "modules/World.h"
#include "df/map_block.h"
#include "df/unit.h"
#include "df/world.h"
using namespace DFHack;
DFHACK_PLUGIN("fix-unit-occupancy");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(cursor);
REQUIRE_GLOBAL(world);
static int run_interval = 1200; // daily
inline float getClock()
{
return (float)clock() / (float)CLOCKS_PER_SEC;
}
static std::string get_unit_description(df::unit *unit)
{
if (!unit)
return "";
std::string desc;
auto name = Units::getVisibleName(unit);
if (name->has_name)
desc = Translation::TranslateName(name, false);
desc += (desc.size() ? ", " : "") + Units::getProfessionName(unit); // Check animal type too
return desc;
}
df::unit *findUnit(int x, int y, int z)
{
for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u)
{
if ((**u).pos.x == x && (**u).pos.y == y && (**u).pos.z == z)
return *u;
}
return NULL;
}
struct uo_opts {
bool dry_run;
bool use_cursor;
uo_opts (bool dry_run = false, bool use_cursor = false)
:dry_run(dry_run), use_cursor(use_cursor)
{}
};
command_result cmd_fix_unit_occupancy (color_ostream &out, std::vector <std::string> & parameters);
unsigned fix_unit_occupancy (color_ostream &out, uo_opts &opts)
{
if (!Core::getInstance().isWorldLoaded())
return 0;
if (opts.use_cursor && cursor->x < 0)
out.printerr("No cursor\n");
unsigned count = 0;
float time1 = getClock();
for (size_t i = 0; i < world->map.map_blocks.size(); i++)
{
df::map_block *block = world->map.map_blocks[i];
int map_z = block->map_pos.z;
if (opts.use_cursor && (map_z != cursor->z || block->map_pos.y != (cursor->y / 16) * 16 || block->map_pos.x != (cursor->x / 16) * 16))
continue;
for (int x = 0; x < 16; x++)
{
int map_x = x + block->map_pos.x;
for (int y = 0; y < 16; y++)
{
if (block->designation[x][y].bits.hidden)
continue;
int map_y = y + block->map_pos.y;
if (opts.use_cursor && (map_x != cursor->x || map_y != cursor->y))
continue;
bool cur_occupancy = block->occupancy[x][y].bits.unit;
bool fixed_occupancy = cur_occupancy;
df::unit *cur_unit = findUnit(map_x, map_y, map_z);
if (cur_occupancy && !cur_unit)
{
out.print("%sFixing occupancy at (%i, %i, %i) - no unit found\n",
opts.dry_run ? "(Dry run) " : "",
map_x, map_y, map_z);
fixed_occupancy = false;
}
// else if (!cur_occupancy && cur_unit)
// {
// out.printerr("Unit found at (%i, %i, %i): %s\n", map_x, map_y, map_z, get_unit_description(cur_unit).c_str());
// fixed_occupancy = true;
// }
if (cur_occupancy != fixed_occupancy && !opts.dry_run)
{
++count;
block->occupancy[x][y].bits.unit = fixed_occupancy;
}
}
}
}
float time2 = getClock();
std::cerr << "fix-unit-occupancy: elapsed time: " << time2 - time1 << " secs" << endl;
if (count)
out << "Fixed occupancy of " << count << " tiles [fix-unit-occupancy]" << endl;
return count;
}
unsigned fix_unit_occupancy (color_ostream &out)
{
uo_opts tmp;
return fix_unit_occupancy(out, tmp);
}
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"fix-unit-occupancy",
"Fix unit occupancy issues such as phantom 'creature blocking site' messages (bug 3499)",
cmd_fix_unit_occupancy,
false, //allow non-interactive use
" enable fix-unit-occupancy: Enable the plugin\n"
" disable fix-unit-occupancy fix-unit-occupancy: Disable the plugin\n"
" fix-unit-occupancy: Run the plugin immediately. Available options:\n"
" -h|here|cursor: Only operate on the tile at the cursor\n"
" -n|dry|dry-run: Do not write changes to map\n"
" fix-unit-occupancy interval X: Run the plugin every X ticks (when enabled).\n"
" Default is 1200, or 1 day. Ticks are only counted when the game is unpaused.\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out)
{
return CR_OK;
}
DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
{
is_enabled = enable;
if (is_enabled)
fix_unit_occupancy(out);
return CR_OK;
}
DFhackCExport command_result plugin_onupdate (color_ostream &out)
{
static unsigned tick = UINT_MAX;
static decltype(world->frame_counter) old_world_frame = 0;
if (is_enabled && World::isFortressMode())
{
// only increment tick when the world has changed
if (old_world_frame != world->frame_counter)
{
old_world_frame = world->frame_counter;
tick++;
}
if (tick > run_interval)
{
tick = 0;
fix_unit_occupancy(out);
}
}
else
{
tick = INT_MAX;
old_world_frame = 0;
}
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event evt)
{
if (evt == SC_MAP_LOADED && is_enabled && World::isFortressMode())
fix_unit_occupancy(out);
return CR_OK;
}
command_result cmd_fix_unit_occupancy (color_ostream &out, std::vector <std::string> & parameters)
{
CoreSuspender suspend;
uo_opts opts;
bool ok = true;
if (parameters.size() >= 1 && (parameters[0] == "-i" || parameters[0].find("interval") != std::string::npos))
{
if (parameters.size() >= 2)
{
int new_interval = atoi(parameters[1].c_str());
if (new_interval < 100)
{
out.printerr("Invalid interval - minimum is 100 ticks\n");
return CR_WRONG_USAGE;
}
run_interval = new_interval;
if (!is_enabled)
out << "note: Plugin not enabled (use `enable fix-unit-occupancy` to enable)" << endl;
return CR_OK;
}
else
return CR_WRONG_USAGE;
}
for (auto opt = parameters.begin(); opt != parameters.end(); ++opt)
{
if (*opt == "-n" || opt->find("dry") != std::string::npos)
opts.dry_run = true;
else if (*opt == "-h" || opt->find("cursor") != std::string::npos || opt->find("here") != std::string::npos)
opts.use_cursor = true;
else if (opt->find("enable") != std::string::npos)
plugin_enable(out, true);
else if (opt->find("disable") != std::string::npos)
plugin_enable(out, false);
else
{
out.printerr("Unknown parameter: %s\n", opt->c_str());
ok = false;
}
}
if (!ok)
return CR_WRONG_USAGE;
fix_unit_occupancy(out, opts);
return CR_OK;
}