diff --git a/data/init/dfhack.tools.init b/data/init/dfhack.tools.init index 38221883a..02169b526 100644 --- a/data/init/dfhack.tools.init +++ b/data/init/dfhack.tools.init @@ -80,6 +80,7 @@ # Enable system services enable buildingplan +enable burrow enable confirm enable logistics enable overlay diff --git a/docs/changelog.txt b/docs/changelog.txt index 19d87faad..8a06ee1a1 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,9 +52,11 @@ Template for new versions: # Future ## New Tools +- `burrow`: (reinstated) automatically expand burrows as you dig ## New Features - `prospect`: can now give you an estimate of resources from the embark screen. hover the mouse over a potential embark area and run `prospect`. +- `burrow`: integrated 3d box fill and 2d/3d flood fill extensions for burrow painting mode ## Fixes - `stockpiles`: hide configure and help buttons when the overlay panel is minimized diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index eeb0fbda6..dd057fe01 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -5612,51 +5612,6 @@ Native functions provided by the `buildingplan` plugin: * ``void doCycle()`` runs a check for whether buildings in the monitor list can be assigned items and unsuspended. This method runs automatically twice a game day, so you only need to call it directly if you want buildingplan to do a check right now. * ``void scheduleCycle()`` schedules a cycle to be run during the next non-paused game frame. Can be called multiple times while the game is paused and only one cycle will be scheduled. -burrow -====== - -The `burrow` plugin implements extended burrow manipulations. - -Events: - -* ``onBurrowRename.foo = function(burrow)`` - - Emitted when a burrow might have been renamed either through - the game UI, or ``renameBurrow()``. - -* ``onDigComplete.foo = function(job_type,pos,old_tiletype,new_tiletype,worker)`` - - Emitted when a tile might have been dug out. Only tracked if the - auto-growing burrows feature is enabled. - -Native functions: - -* ``renameBurrow(burrow,name)`` - - Renames the burrow, emitting ``onBurrowRename`` and updating auto-grow state properly. - -* ``findByName(burrow,name)`` - - Finds a burrow by name, using the same rules as the plugin command line interface. - Namely, trailing ``'+'`` characters marking auto-grow burrows are ignored. - -* ``copyUnits(target,source,enable)`` - - Applies units from ``source`` burrow to ``target``. The ``enable`` - parameter specifies if they are to be added or removed. - -* ``copyTiles(target,source,enable)`` - - Applies tiles from ``source`` burrow to ``target``. The ``enable`` - parameter specifies if they are to be added or removed. - -* ``setTilesByKeyword(target,keyword,enable)`` - - Adds or removes tiles matching a predefined keyword. The keyword - set is the same as used by the command line. - -The lua module file also re-exports functions from ``dfhack.burrows``. - .. _cxxrandom-api: cxxrandom diff --git a/docs/plugins/burrow.rst b/docs/plugins/burrow.rst index 68556053b..f84f02989 100644 --- a/docs/plugins/burrow.rst +++ b/docs/plugins/burrow.rst @@ -7,7 +7,9 @@ burrow This tool has two modes. When enabled, it monitors burrows with names that end in ``+``. If a wall at the edge of such a burrow is dug out, the burrow will be -automatically extended to include the newly-revealed adjacent walls. +automatically extended to include the newly-revealed adjacent walls. If a miner +digs into an open space, such as a cavern, the open space will *not* be +included in the burrow. When run as a command, it can quickly adjust which tiles and/or units are associated with the burrow. diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 14fac42b3..e985cfa96 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -206,6 +206,9 @@ std::array compileManagerArray() { //job initiated static int32_t lastJobId = -1; +//job started +static unordered_set startedJobs; + //job completed static unordered_map prevJobs; @@ -269,6 +272,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event } if ( event == DFHack::SC_MAP_UNLOADED ) { lastJobId = -1; + startedJobs.clear(); for (auto &prevJob : prevJobs) { Job::deleteJobStruct(prevJob.second, true); } @@ -461,29 +465,27 @@ static void manageJobStartedEvent(color_ostream& out) { if (!df::global::world) return; - static unordered_set startedJobs; - - vector new_started_jobs; // iterate event handler callbacks multimap copy(handlers[EventType::JOB_STARTED].begin(), handlers[EventType::JOB_STARTED].end()); + unordered_set newStartedJobs; + for (df::job_list_link* link = &df::global::world->jobs.list; link->next != nullptr; link = link->next) { df::job* job = link->next->item; + if (!job || !Job::getWorker(job)) + continue; + int32_t j_id = job->id; - if (job && Job::getWorker(job) && !startedJobs.count(job->id)) { - startedJobs.emplace(job->id); + newStartedJobs.emplace(j_id); + if (!startedJobs.count(j_id)) { for (auto &[_,handle] : copy) { - // the jobs must have a worker to start DEBUG(log,out).print("calling handler for job started event\n"); handle.eventHandler(out, job); } } - if (link->next == nullptr || link->next->item->id != j_id) { - if ( Once::doOnce("EventManager jobstarted job removed") ) { - out.print("%s,%d: job %u removed from jobs linked list\n", __FILE__, __LINE__, j_id); - } - } } + + startedJobs = newStartedJobs; } //helper function for manageJobCompletedEvent @@ -498,6 +500,7 @@ TODO: consider checking item creation / experience gain just in case static void manageJobCompletedEvent(color_ostream& out) { if (!df::global::world) return; + int32_t tick0 = eventLastTick[EventType::JOB_COMPLETED]; int32_t tick1 = df::global::world->frame_counter; diff --git a/plugins/burrow.cpp b/plugins/burrow.cpp index ef6231990..f33e9e910 100644 --- a/plugins/burrow.cpp +++ b/plugins/burrow.cpp @@ -5,12 +5,15 @@ #include "TileTypes.h" #include "modules/Burrows.h" +#include "modules/EventManager.h" +#include "modules/Job.h" #include "modules/Persistence.h" #include "modules/World.h" #include "df/block_burrow.h" #include "df/burrow.h" #include "df/map_block.h" +#include "df/plotinfost.h" #include "df/tile_designation.h" #include "df/unit.h" #include "df/world.h" @@ -22,6 +25,7 @@ using namespace DFHack; DFHACK_PLUGIN("burrow"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(plotinfo); REQUIRE_GLOBAL(window_z); REQUIRE_GLOBAL(world); @@ -30,35 +34,15 @@ namespace DFHack { // for configuration-related logging DBG_DECLARE(burrow, status, DebugCategory::LINFO); // for logging during the periodic scan - DBG_DECLARE(burrow, cycle, DebugCategory::LINFO); -} - -static const auto CONFIG_KEY = std::string(plugin_name) + "/config"; -static PersistentDataItem config; -enum ConfigValues { - CONFIG_IS_ENABLED = 0, -}; -static int get_config_val(int index) { - if (!config.isValid()) - return -1; - return config.ival(index); -} -static bool get_config_bool(int index) { - return get_config_val(index) == 1; -} -static void set_config_val(int index, int value) { - if (config.isValid()) - config.ival(index) = value; -} -static void set_config_bool(int index, bool value) { - set_config_val(index, value ? 1 : 0); + DBG_DECLARE(burrow, event, DebugCategory::LINFO); } -static const int32_t CYCLE_TICKS = 100; -static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle +static std::unordered_map active_dig_jobs; static command_result do_command(color_ostream &out, vector ¶meters); -static void do_cycle(color_ostream &out); +static void init_diggers(color_ostream& out); +static void jobStartedHandler(color_ostream& out, void* ptr); +static void jobCompletedHandler(color_ostream& out, void* ptr); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { DEBUG(status, out).print("initializing %s\n", plugin_name); @@ -69,18 +53,21 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vectorframe_counter - cycle_timestamp >= CYCLE_TICKS) - do_cycle(out); + if (event == DFHack::SC_WORLD_UNLOADED) + reset(); return CR_OK; } @@ -172,15 +131,107 @@ static command_result do_command(color_ostream &out, vector ¶meters) } ///////////////////////////////////////////////////// -// cycle logic +// listener logic // -static void do_cycle(color_ostream &out) +static void init_diggers(color_ostream& out) { + if (!Core::getInstance().isWorldLoaded()) { + DEBUG(status, out).print("world not yet loaded; not scanning jobs\n"); + return; + } + + std::vector pvec; + int start_id = 0; + if (Job::listNewlyCreated(&pvec, &start_id)) { + for (auto job : pvec) { + if (Job::getWorker(job)) + jobStartedHandler(out, job); + } + } +} + +static void jobStartedHandler(color_ostream& out, void* ptr) { + DEBUG(event, out).print("entering jobStartedHandler\n"); + + df::job *job = (df::job *)ptr; + auto type = ENUM_ATTR(job_type, type, job->job_type); + if (type != job_type_class::Digging) + return; + + const df::coord &pos = job->pos; + DEBUG(event, out).print("dig job started: id=%d, pos=(%d,%d,%d), type=%s\n", + job->id, pos.x, pos.y, pos.z, ENUM_KEY_STR(job_type, job->job_type).c_str()); + df::tiletype *tt = Maps::getTileType(pos); + if (tt) + active_dig_jobs[pos] = *tt; +} + +static void add_walls_to_burrow(color_ostream &out, df::burrow* b, + const df::coord & pos1, const df::coord & pos2) { - // mark that we have recently run - cycle_timestamp = world->frame_counter; + for (int z = pos1.z; z <= pos2.z; z++) { + for (int y = pos1.y; y <= pos2.y; y++) { + for (int x = pos1.x; x <= pos2.x; x++) { + df::coord pos(x,y,z); + df::tiletype *tt = Maps::getTileType(pos); + if (tt && isWallTerrain(*tt)) + Burrows::setAssignedTile(b, pos, true); + } + } + } +} + +static void expand_burrows(color_ostream &out, const df::coord & pos, df::tiletype prev_tt, df::tiletype tt) { + if (!isWalkable(tt)) + return; + + bool changed = false; + for (auto b : plotinfo->burrows.list) { + if (!b->name.ends_with('+') || !Burrows::isAssignedTile(b, pos)) + continue; + + if (!isWalkable(prev_tt)) { + changed = true; + add_walls_to_burrow(out, b, pos+df::coord(-1,-1,0), pos+df::coord(1,1,0)); + + if (isWalkableUp(tt)) + Burrows::setAssignedTile(b, pos+df::coord(0,0,1), true); + + if (tileShape(tt) == tiletype_shape::RAMP) + add_walls_to_burrow(out, b, pos+df::coord(-1,-1,1), pos+df::coord(1,1,1)); + } + + if (LowPassable(tt) && !LowPassable(prev_tt)) { + changed = true; + Burrows::setAssignedTile(b, pos-df::coord(0,0,1), true); + if (tileShape(tt) == tiletype_shape::RAMP_TOP) + add_walls_to_burrow(out, b, pos+df::coord(-1,-1,-1), pos+df::coord(1,1,-1)); + } + } + + if (changed) + Job::checkDesignationsNow(); +} + +static void jobCompletedHandler(color_ostream& out, void* ptr) { + DEBUG(event, out).print("entering jobCompletedHandler\n"); + + df::job *job = (df::job *)ptr; + auto type = ENUM_ATTR(job_type, type, job->job_type); + if (type != job_type_class::Digging) + return; + + const df::coord &pos = job->pos; + DEBUG(event, out).print("dig job completed: id=%d, pos=(%d,%d,%d), type=%s\n", + job->id, pos.x, pos.y, pos.z, ENUM_KEY_STR(job_type, job->job_type).c_str()); + + df::tiletype prev_tt = active_dig_jobs[pos]; + df::tiletype *tt = Maps::getTileType(pos); + + if (tt && *tt && *tt != prev_tt) + expand_burrows(out, pos, prev_tt, *tt); - // TODO + active_dig_jobs.erase(pos); } ///////////////////////////////////////////////////// @@ -633,423 +684,3 @@ DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(burrow_units_remove), DFHACK_LUA_END }; - - -/* -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include "Error.h" - -#include "DataFuncs.h" -#include "LuaTools.h" - -#include "modules/Gui.h" -#include "modules/Job.h" -#include "modules/Maps.h" -#include "modules/MapCache.h" -#include "modules/World.h" -#include "modules/Units.h" -#include "TileTypes.h" - -#include "DataDefs.h" -#include "df/plotinfost.h" -#include "df/world.h" -#include "df/unit.h" -#include "df/burrow.h" -#include "df/map_block.h" -#include "df/block_burrow.h" -#include "df/job.h" -#include "df/job_list_link.h" - -#include "MiscUtils.h" - -#include - -using std::vector; -using std::string; -using std::endl; -using namespace DFHack; -using namespace df::enums; -using namespace dfproto; - -DFHACK_PLUGIN("burrow"); -REQUIRE_GLOBAL(plotinfo); -REQUIRE_GLOBAL(world); -REQUIRE_GLOBAL(gamemode); - -static void init_map(color_ostream &out); -static void deinit_map(color_ostream &out); - -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) -{ - - if (Core::getInstance().isMapLoaded()) - init_map(out); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - deinit_map(out); - - return CR_OK; -} - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) { - case SC_MAP_LOADED: - deinit_map(out); - if (gamemode && - *gamemode == game_mode::DWARF) - init_map(out); - break; - case SC_MAP_UNLOADED: - deinit_map(out); - break; - default: - break; - } - - return CR_OK; -} - -static int name_burrow_id = -1; - -static void handle_burrow_rename(color_ostream &out, df::burrow *burrow); - -DEFINE_LUA_EVENT_1(onBurrowRename, handle_burrow_rename, df::burrow*); - -static void detect_burrow_renames(color_ostream &out) -{ - if (plotinfo->main.mode == ui_sidebar_mode::Burrows && - plotinfo->burrows.in_edit_name_mode && - plotinfo->burrows.sel_id >= 0) - { - name_burrow_id = plotinfo->burrows.sel_id; - } - else if (name_burrow_id >= 0) - { - auto burrow = df::burrow::find(name_burrow_id); - name_burrow_id = -1; - if (burrow) - onBurrowRename(out, burrow); - } -} - -struct DigJob { - int id; - df::job_type job; - df::coord pos; - df::tiletype old_tile; -}; - -static int next_job_id_save = 0; -static std::map diggers; - -static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos, - df::tiletype old_tile, df::tiletype new_tile, df::unit *worker); - -DEFINE_LUA_EVENT_5(onDigComplete, handle_dig_complete, - df::job_type, df::coord, df::tiletype, df::tiletype, df::unit*); - -static void detect_digging(color_ostream &out) -{ - for (auto it = diggers.begin(); it != diggers.end();) - { - auto worker = df::unit::find(it->first); - - if (!worker || !worker->job.current_job || - worker->job.current_job->id != it->second.id) - { - //out.print("Dig job %d expired.\n", it->second.id); - - df::coord pos = it->second.pos; - - if (auto block = Maps::getTileBlock(pos)) - { - df::tiletype new_tile = block->tiletype[pos.x&15][pos.y&15]; - - //out.print("Tile %d -> %d\n", it->second.old_tile, new_tile); - - if (new_tile != it->second.old_tile) - { - onDigComplete(out, it->second.job, pos, it->second.old_tile, new_tile, worker); - } - } - - auto cur = it; ++it; diggers.erase(cur); - } - else - ++it; - } - - std::vector jvec; - - if (Job::listNewlyCreated(&jvec, &next_job_id_save)) - { - for (size_t i = 0; i < jvec.size(); i++) - { - auto job = jvec[i]; - auto type = ENUM_ATTR(job_type, type, job->job_type); - if (type != job_type_class::Digging) - continue; - - auto worker = Job::getWorker(job); - if (!worker) - continue; - - df::coord pos = job->pos; - auto block = Maps::getTileBlock(pos); - if (!block) - continue; - - auto &info = diggers[worker->id]; - - //out.print("New dig job %d.\n", job->id); - - info.id = job->id; - info.job = job->job_type; - info.pos = pos; - info.old_tile = block->tiletype[pos.x&15][pos.y&15]; - } - } -} - -DFHACK_PLUGIN_IS_ENABLED(active); - -static bool auto_grow = false; -static std::vector grow_burrows; - -DFhackCExport command_result plugin_onupdate(color_ostream &out) -{ - if (!active) - return CR_OK; - - detect_burrow_renames(out); - - if (auto_grow) - detect_digging(out); - - return CR_OK; -} - -static std::map name_lookup; - -static void parse_names() -{ - auto &list = plotinfo->burrows.list; - - grow_burrows.clear(); - name_lookup.clear(); - - for (size_t i = 0; i < list.size(); i++) - { - auto burrow = list[i]; - - std::string name = burrow->name; - - if (!name.empty()) - { - name_lookup[name] = burrow->id; - - if (name[name.size()-1] == '+') - { - grow_burrows.push_back(burrow->id); - name.resize(name.size()-1); - } - - if (!name.empty()) - name_lookup[name] = burrow->id; - } - } -} - -static void reset_tracking() -{ - diggers.clear(); - next_job_id_save = 0; -} - -static void init_map(color_ostream &out) -{ - auto config = World::GetPersistentData("burrows/config"); - if (config.isValid()) - { - auto_grow = !!(config.ival(0) & 1); - } - - parse_names(); - name_burrow_id = -1; - - reset_tracking(); - active = true; - - if (auto_grow && !grow_burrows.empty()) - out.print("Auto-growing %zu burrows.\n", grow_burrows.size()); -} - -static void deinit_map(color_ostream &out) -{ - active = false; - auto_grow = false; - reset_tracking(); -} - -static PersistentDataItem create_config(color_ostream &out) -{ - bool created; - auto rv = World::GetPersistentData("burrows/config", &created); - if (created && rv.isValid()) - rv.ival(0) = 0; - if (!rv.isValid()) - out.printerr("Could not write configuration."); - return rv; -} - -static void enable_auto_grow(color_ostream &out, bool enable) -{ - if (enable == auto_grow) - return; - - auto config = create_config(out); - if (!config.isValid()) - return; - - if (enable) - config.ival(0) |= 1; - else - config.ival(0) &= ~1; - - auto_grow = enable; - - if (enable) - reset_tracking(); -} - -static void handle_burrow_rename(color_ostream &out, df::burrow *burrow) -{ - parse_names(); -} - -static void add_to_burrows(std::vector &burrows, df::coord pos) -{ - for (size_t i = 0; i < burrows.size(); i++) - Burrows::setAssignedTile(burrows[i], pos, true); -} - -static void add_walls_to_burrows(color_ostream &out, std::vector &burrows, - MapExtras::MapCache &mc, df::coord pos1, df::coord pos2) -{ - for (int x = pos1.x; x <= pos2.x; x++) - { - for (int y = pos1.y; y <= pos2.y; y++) - { - for (int z = pos1.z; z <= pos2.z; z++) - { - df::coord pos(x,y,z); - - auto tile = mc.tiletypeAt(pos); - - if (isWallTerrain(tile)) - add_to_burrows(burrows, pos); - } - } - } -} - -static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos, - df::tiletype old_tile, df::tiletype new_tile, df::unit *worker) -{ - if (!isWalkable(new_tile)) - return; - - std::vector to_grow; - - for (size_t i = 0; i < grow_burrows.size(); i++) - { - auto b = df::burrow::find(grow_burrows[i]); - if (b && Burrows::isAssignedTile(b, pos)) - to_grow.push_back(b); - } - - //out.print("%d to grow.\n", to_grow.size()); - - if (to_grow.empty()) - return; - - MapExtras::MapCache mc; - bool changed = false; - - if (!isWalkable(old_tile)) - { - changed = true; - add_walls_to_burrows(out, to_grow, mc, pos+df::coord(-1,-1,0), pos+df::coord(1,1,0)); - - if (isWalkableUp(new_tile)) - add_to_burrows(to_grow, pos+df::coord(0,0,1)); - - if (tileShape(new_tile) == tiletype_shape::RAMP) - { - add_walls_to_burrows(out, to_grow, mc, - pos+df::coord(-1,-1,1), pos+df::coord(1,1,1)); - } - } - - if (LowPassable(new_tile) && !LowPassable(old_tile)) - { - changed = true; - add_to_burrows(to_grow, pos-df::coord(0,0,1)); - - if (tileShape(new_tile) == tiletype_shape::RAMP_TOP) - { - add_walls_to_burrows(out, to_grow, mc, - pos+df::coord(-1,-1,-1), pos+df::coord(1,1,-1)); - } - } - - if (changed && worker && !worker->job.current_job) - Job::checkDesignationsNow(); -} - -static void renameBurrow(color_ostream &out, df::burrow *burrow, std::string name) -{ - CHECK_NULL_POINTER(burrow); - - // The event makes this absolutely necessary - CoreSuspender suspend; - - burrow->name = name; - onBurrowRename(out, burrow); -} - -static df::burrow *findByName(color_ostream &out, std::string name, bool silent = false) -{ - int id = -1; - if (name_lookup.count(name)) - id = name_lookup[name]; - auto rv = df::burrow::find(id); - if (!rv && !silent) - out.printerr("Burrow not found: '%s'\n", name.c_str()); - return rv; -} - -DFHACK_PLUGIN_LUA_FUNCTIONS { - DFHACK_LUA_FUNCTION(renameBurrow), - DFHACK_LUA_FUNCTION(findByName), - DFHACK_LUA_FUNCTION(copyUnits), - DFHACK_LUA_FUNCTION(copyTiles), - DFHACK_LUA_FUNCTION(setTilesByKeyword), - DFHACK_LUA_END -}; - -DFHACK_PLUGIN_LUA_EVENTS { - DFHACK_LUA_EVENT(onBurrowRename), - DFHACK_LUA_EVENT(onDigComplete), - DFHACK_LUA_END -}; - -*/ diff --git a/plugins/lua/burrow.lua b/plugins/lua/burrow.lua index be4a51dd4..5248beb31 100644 --- a/plugins/lua/burrow.lua +++ b/plugins/lua/burrow.lua @@ -1,14 +1,5 @@ local _ENV = mkmodule('plugins.burrow') ---[[ - - Provided events: - - * onBurrowRename(burrow) - * onDigComplete(job_type,pos,old_tiletype,new_tiletype) - ---]] - local argparse = require('argparse') local overlay = require('plugins.overlay') local widgets = require('gui.widgets')