implement autogrow and update docs

also fix bad state tracking in EventManager for the JobStarted event
develop
Myk Taylor 2023-11-02 12:44:37 -07:00
parent a2735125bc
commit 940f7de0d2
No known key found for this signature in database
7 changed files with 141 additions and 556 deletions

@ -80,6 +80,7 @@
# Enable system services
enable buildingplan
enable burrow
enable confirm
enable logistics
enable overlay

@ -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

@ -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

@ -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.

@ -206,6 +206,9 @@ std::array<eventManager_t,EventType::EVENT_MAX> compileManagerArray() {
//job initiated
static int32_t lastJobId = -1;
//job started
static unordered_set<int32_t> startedJobs;
//job completed
static unordered_map<int32_t, df::job*> 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<int32_t> startedJobs;
vector<df::job*> new_started_jobs;
// iterate event handler callbacks
multimap<Plugin*, EventHandler> copy(handlers[EventType::JOB_STARTED].begin(), handlers[EventType::JOB_STARTED].end());
unordered_set<int32_t> 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;

@ -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<df::coord, df::tiletype> active_dig_jobs;
static command_result do_command(color_ostream &out, vector<string> &parameters);
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<PluginCommand> &commands) {
DEBUG(status, out).print("initializing %s\n", plugin_name);
@ -69,18 +53,21 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginC
return CR_OK;
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot enable %s without a loaded world.\n", plugin_name);
return CR_FAILURE;
}
static void reset() {
EventManager::unregisterAll(plugin_self);
active_dig_jobs.clear();
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
if (enable != is_enabled) {
is_enabled = enable;
DEBUG(status, out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled");
set_config_bool(CONFIG_IS_ENABLED, is_enabled);
if (enable)
do_cycle(out);
DEBUG(status, out).print("%s from the API\n", is_enabled ? "enabled" : "disabled");
reset();
if (enable) {
init_diggers(out);
EventManager::registerListener(EventManager::EventType::JOB_STARTED, EventManager::EventHandler(jobStartedHandler, 0), plugin_self);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, EventManager::EventHandler(jobCompletedHandler, 0), plugin_self);
}
}
else {
DEBUG(status, out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled");
@ -90,41 +77,13 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
DFhackCExport command_result plugin_shutdown(color_ostream &out) {
DEBUG(status, out).print("shutting down %s\n", plugin_name);
return CR_OK;
}
DFhackCExport command_result plugin_load_data(color_ostream &out) {
cycle_timestamp = 0;
config = World::GetPersistentData(CONFIG_KEY);
if (!config.isValid()) {
DEBUG(status, out).print("no config found in this save; initializing\n");
config = World::AddPersistentData(CONFIG_KEY);
set_config_bool(CONFIG_IS_ENABLED, is_enabled);
}
// we have to copy our enabled flag into the global plugin variable, but
// all the other state we can directly read/modify from the persistent
// data structure.
is_enabled = get_config_bool(CONFIG_IS_ENABLED);
DEBUG(status, out).print("loading persisted enabled state: %s\n", is_enabled ? "true" : "false");
reset();
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
if (event == DFHack::SC_WORLD_UNLOADED) {
if (is_enabled) {
DEBUG(status, out).print("world unloaded; disabling %s\n", plugin_name);
is_enabled = false;
}
}
return CR_OK;
}
DFhackCExport command_result plugin_onupdate(color_ostream &out) {
CoreSuspender suspend;
if (is_enabled && world->frame_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<string> &parameters)
}
/////////////////////////////////////////////////////
// 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<df::job*> 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 <stdlib.h>
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 <PluginCommand> &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<int,DigJob> 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<df::job*> 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<int> 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<std::string,int> 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<df::burrow*> &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<df::burrow*> &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<df::burrow*> 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
};
*/

@ -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')