diff --git a/library/include/TileTypes.h b/library/include/TileTypes.h index 26df7c3cd..55628b3df 100644 --- a/library/include/TileTypes.h +++ b/library/include/TileTypes.h @@ -203,6 +203,18 @@ namespace DFHack return ENUM_ATTR(tiletype_shape, passable_flow, tileShape(tiletype)); } + inline + bool isWalkable(df::tiletype tiletype) + { + return ENUM_ATTR(tiletype_shape, walkable, tileShape(tiletype)); + } + + inline + bool isWalkableUp(df::tiletype tiletype) + { + return ENUM_ATTR(tiletype_shape, walkable_up, tileShape(tiletype)); + } + inline bool isWallTerrain(df::tiletype tiletype) { diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 0de262264..3cd1187a5 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -262,12 +262,12 @@ extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, d DFHACK_EXPORT df::burrow *findBurrowByName(std::string name); -void listBurrowBlocks(std::vector *pvec, df::burrow *burrow); +DFHACK_EXPORT void listBurrowBlocks(std::vector *pvec, df::burrow *burrow); -df::block_burrow *getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create = false); +DFHACK_EXPORT df::block_burrow *getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create = false); -bool isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile); -bool setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable); +DFHACK_EXPORT bool isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile); +DFHACK_EXPORT bool setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable); inline bool isBurrowTile(df::burrow *burrow, df::coord tile) { return isBlockBurrowTile(burrow, getTileBlock(tile), tile); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6c2a537d1..d30d25697 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -97,6 +97,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(feature feature.cpp) DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(zone zone.cpp) + DFHACK_PLUGIN(burrows burrows.cpp) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) endif() diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp new file mode 100644 index 000000000..a559573ac --- /dev/null +++ b/plugins/burrows.cpp @@ -0,0 +1,413 @@ +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/Gui.h" +#include "modules/Job.h" +#include "modules/Maps.h" +#include "modules/MapCache.h" +#include "modules/World.h" +#include "TileTypes.h" + +#include "DataDefs.h" +#include "df/ui.h" +#include "df/world.h" +#include "df/unit.h" +#include "df/burrow.h" +#include "df/map_block.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; + +using df::global::ui; +using df::global::world; + +/* + * Initialization. + */ + +static command_result burrow(color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("burrows"); + +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) +{ + commands.push_back(PluginCommand( + "burrow", "Miscellaneous burrow control.", burrow, false, + " burrow enable options...\n" + " burrow disable options...\n" + " Enable or disable features of the plugin.\n" + " See below for a list and explanation.\n" + "Implemented features:\n" + " auto-grow\n" + " When a wall inside a burrow with a name ending in '+' is dug\n" + " out, the burrow is extended to newly-revealed adjacent walls.\n" + " Digging 1-wide corridors with the miner inside the burrow is SLOW.\n" + )); + + 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 (df::global::game_mode && + *df::global::game_mode == GAMEMODE_DWARF) + init_map(out); + break; + case SC_MAP_UNLOADED: + deinit_map(out); + break; + default: + break; + } + + return CR_OK; +} + +/* + * State change tracking. + */ + +static int name_burrow_id = -1; + +static void handle_burrow_rename(color_ostream &out, df::burrow *burrow); + +static void detect_burrow_renames(color_ostream &out) +{ + if (ui->main.mode == ui_sidebar_mode::Burrows && + ui->burrows.in_edit_name_mode && + ui->burrows.sel_id >= 0) + { + name_burrow_id = ui->burrows.sel_id; + } + else if (name_burrow_id >= 0) + { + auto burrow = df::burrow::find(name_burrow_id); + name_burrow_id = -1; + if (burrow) + handle_burrow_rename(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); + +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) + { + handle_dig_complete(out, it->second.job, pos, it->second.old_tile, new_tile); + + //if (worker && !worker->job.current_job) + // worker->counters.think_counter = worker->counters.job_counter = 0; + } + } + + 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]; + } + } +} + +static bool active = false; +static bool auto_grow = false; +static std::vector grow_burrows; + +DFhackCExport command_result plugin_onupdate(color_ostream &out) +{ + if (!active || !auto_grow) + return CR_OK; + + detect_burrow_renames(out); + detect_digging(out); + return CR_OK; +} + +/* + * Config and processing + */ + +static void parse_names() +{ + auto &list = ui->burrows.list; + + grow_burrows.clear(); + + for (size_t i = 0; i < list.size(); i++) + { + auto burrow = list[i]; + + if (!burrow->name.empty() && burrow->name[burrow->name.size()-1] == '+') + grow_burrows.push_back(burrow->id); + } +} + +static void reset_tracking() +{ + name_burrow_id = -1; + diggers.clear(); + next_job_id_save = 0; +} + +static void init_map(color_ostream &out) +{ + auto config = Core::getInstance().getWorld()->GetPersistentData("burrows/config"); + if (config.isValid()) + { + auto_grow = !!(config.ival(0) & 1); + } + + parse_names(); + reset_tracking(); + active = true; + + if (auto_grow && !grow_burrows.empty()) + out.print("Auto-growing %d 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 = Core::getInstance().getWorld()->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) + { + parse_names(); + 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++) + Maps::setBurrowTile(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) +{ + 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 && Maps::isBurrowTile(b, pos)) + to_grow.push_back(b); + } + + //out.print("%d to grow.\n", to_grow.size()); + + if (to_grow.empty()) + return; + + MapExtras::MapCache mc; + + if (!isWalkable(old_tile)) + { + 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)) + { + 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)); + } + } +} + +static command_result burrow(color_ostream &out, vector ¶meters) +{ + CoreSuspender suspend; + + if (!active) + { + out.printerr("The plugin cannot be used without map.\n"); + return CR_FAILURE; + } + + string cmd; + if (!parameters.empty()) + cmd = parameters[0]; + + if (cmd == "enable" || cmd == "disable") + { + if (parameters.size() < 2) + return CR_WRONG_USAGE; + + bool state = (cmd == "enable"); + + for (int i = 1; i < parameters.size(); i++) + { + string &option = parameters[i]; + + if (option == "auto-grow") + enable_auto_grow(out, state); + else + return CR_WRONG_USAGE; + } + } + else + { + if (!parameters.empty() && cmd != "?") + out.printerr("Invalid command: %s\n", cmd.c_str()); + return CR_WRONG_USAGE; + } + + return CR_OK; +}