Merge pull request #3935 from myk002/myk_update_burrow

[burrow] update and integrate with UI
develop
Myk 2023-10-31 10:47:07 -07:00 committed by GitHub
commit 748d8d8222
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 820 additions and 66 deletions

@ -1,46 +1,36 @@
burrows
=======
burrow
======
.. dfhack-tool::
:summary: Auto-expand burrows as you dig.
:tags: unavailable
:no-command:
:summary: Quickly adjust burrow tiles and units.
:tags: fort auto design productivity units
.. dfhack-command:: burrow
:summary: Quickly add units/tiles to burrows.
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.
When a wall inside a burrow with a name ending in ``+`` is dug out, the burrow
will be extended to newly-revealed adjacent walls.
When run as a command, it can quickly adjust which tiles and/or units are
associated with the burrow.
Usage
-----
``burrow enable auto-grow``
When a wall inside a burrow with a name ending in '+' is dug out, the burrow
will be extended to newly-revealed adjacent walls. This final '+' may be
omitted in burrow name args of other ``burrow`` commands. Note that digging
1-wide corridors with the miner inside the burrow is SLOW.
``burrow disable auto-grow``
Disables auto-grow processing.
``burrow clear-unit <burrow> [<burrow> ...]``
Remove all units from the named burrows.
``burrow clear-tiles <burrow> [<burrow> ...]``
Remove all tiles from the named burrows.
``burrow set-units target-burrow <burrow> [<burrow> ...]``
Clear all units from the target burrow, then add units from the named source
burrows.
``burrow add-units target-burrow <burrow> [<burrow> ...]``
Add units from the source burrows to the target.
``burrow remove-units target-burrow <burrow> [<burrow> ...]``
Remove units in source burrows from the target.
``burrow set-tiles target-burrow <burrow> [<burrow> ...]``
Clear target burrow tiles and add tiles from the names source burrows.
``burrow add-tiles target-burrow <burrow> [<burrow> ...]``
Add tiles from the source burrows to the target.
``burrow remove-tiles target-burrow <burrow> [<burrow> ...]``
Remove tiles in source burrows from the target.
In place of a source burrow, you can use one of the following keywords:
::
enable burrow
burrow tiles|units clear <target burrow> [<target burrow> ...] [<options>]
burrow tiles|units set|add|remove <target burrow> <burrow> [...] [<options>]
burrow tiles box-add|box-remove <target burrow> [<pos>] [<pos>] [<options>]
burrow tiles flood-add|flood-remove <target burrow> [<options>]
The burrows can be referenced by name or by the internal numeric burrow ID. If
referenced by name, the first burrow that matches the name (case sensitive)
will be targeted. If a burrow name ends in ``+`` (to indicate that it should be
auto-expanded), the final ``+`` does not need to be specified on the
commandline.
For ``set``, ``add``, or ``remove`` commands, instead of a burrow, you can
specify one of the following all-caps keywords:
- ``ABOVE_GROUND``
- ``SUBTERRANEAN``
@ -51,4 +41,75 @@ In place of a source burrow, you can use one of the following keywords:
- ``HIDDEN``
- ``REVEALED``
to add tiles with the given properties.
to add or remove tiles with the corresponding properties.
Flood fill selects tiles spreading out from a starting tile if they:
- match the inside/outside and hidden/revealed properties of the starting tile
- match the walkability group of the starting tile OR (if the starting tile is
walkable) is adjacent to a tile with the same walkability group as the
starting tile
When flood adding, the flood fill will also stop at any tiles that have already
been added to the burrow. Similarly for flood removing, the flood will also
stop at tiles that are not in the burrow.
Examples
--------
``enable burrow``
Start monitoring burrows that have names ending in '+' and automatically
expand them when walls that border the burrows are dug out.
``burrow tiles clear Safety``
Remove all tiles from the burrow named ``Safety`` (in preparation for
adding new tiles elsewhere, presumably).
``burrow units clear Farmhouse Workshops``
Remove all units from the burrows named ``Farmhouse`` and ``Workshops``.
``multicmd burrow tiles set Inside INSIDE; burrow tiles remove Inside HIDDEN``
Reset the burrow named ``Inside`` to include all the currently revealed,
interior tiles.
``burrow units set "Core Fort" Peasants Skilled``
Clear all units from the burrow named ``Core Fort``, then add units
currently assigned to the ``Peasants`` and ``Skilled`` burrows.
``burrow tiles box-add Safety 0,0,0``
Add all tiles to the burrow named ``Safety`` that are within the volume of
the box starting at coordinate 0, 0, 0 (the upper left corner of the bottom
level) and ending at the current location of the keyboard cursor.
``burrow tiles flood-add Safety --cur-zlevel``
Flood-add the tiles on the current z-level with the same properties as the
tile under the keyboard cursor to the burrow named ``Safety``.
Options
-------
``-c``, ``--cursor <pos>``
Indicate the starting position of the box or flood fill. If not specified,
the position of the keyboard cursor is used.
``-d``, ``--dry-run``
Report what would be done, but don't actually change anything.
``-z``, ``--cur-zlevel``
Restricts the operation to the currently visible z-level.
Note
----
If you are auto-expanding a burrow (whose name ends in a ``+``) and the miner
who is digging to expand the burrow is assigned to that burrow, then 1-wide
corridors that expand the burrow will have very slow progress. This is because
the burrow is expanded to include the next dig job only after the miner has
chosen a next tile to dig, which may be far away. 2-wide cooridors are much
more efficient when expanding a burrow since the "next" tile to dig will still
be nearby.
Overlay
-------
When painting burrows in the vanilla UI, a few extra mouse operations are
supported. If you box select across multiple z-levels, you will be able to
select the entire volume instead of just the selected area on the z-level that
you are currently looking at.
In addition, double-clicking will start a flood fill from the target tile.
The box and flood fill actions respect the UI setting for whether the burrow is
being added to or erased.

@ -90,7 +90,7 @@ if(BUILD_SUPPORTED)
dfhack_plugin(autonestbox autonestbox.cpp LINK_LIBRARIES lua)
dfhack_plugin(autoslab autoslab.cpp)
dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua)
#dfhack_plugin(burrows burrows.cpp LINK_LIBRARIES lua)
dfhack_plugin(burrow burrow.cpp LINK_LIBRARIES lua)
#dfhack_plugin(building-hacks building-hacks.cpp LINK_LIBRARIES lua)
add_subdirectory(buildingplan)
dfhack_plugin(changeitem changeitem.cpp)

@ -1,3 +1,496 @@
#include "Core.h"
#include "Debug.h"
#include "LuaTools.h"
#include "PluginManager.h"
#include "TileTypes.h"
#include "modules/Burrows.h"
#include "modules/Persistence.h"
#include "modules/World.h"
#include "df/burrow.h"
#include "df/tile_designation.h"
#include "df/world.h"
using std::vector;
using std::string;
using namespace DFHack;
DFHACK_PLUGIN("burrow");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(window_z);
REQUIRE_GLOBAL(world);
// logging levels can be dynamically controlled with the `debugfilter` command.
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);
}
static const int32_t CYCLE_TICKS = 100;
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
static command_result do_command(color_ostream &out, vector<string> &parameters);
static void do_cycle(color_ostream &out);
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) {
DEBUG(status, out).print("initializing %s\n", plugin_name);
commands.push_back(
PluginCommand("burrow",
"Quickly adjust burrow tiles and units.",
do_command));
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;
}
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);
}
else {
DEBUG(status, out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled");
}
return CR_OK;
}
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");
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);
return CR_OK;
}
static bool call_burrow_lua(color_ostream *out, const char *fn_name,
int nargs = 0, int nres = 0,
Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA,
Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) {
DEBUG(status).print("calling %s lua function: '%s'\n", plugin_name, fn_name);
CoreSuspender guard;
auto L = Lua::Core::State;
Lua::StackUnwinder top(L);
if (!out)
out = &Core::getInstance().getConsole();
return Lua::CallLuaModuleFunction(*out, L, "plugins.burrow", fn_name,
nargs, nres,
std::forward<Lua::LuaLambda&&>(args_lambda),
std::forward<Lua::LuaLambda&&>(res_lambda));
}
static command_result do_command(color_ostream &out, vector<string> &parameters) {
CoreSuspender suspend;
if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
return CR_FAILURE;
}
bool show_help = false;
if (!call_burrow_lua(&out, "parse_commandline", parameters.size(), 1,
[&](lua_State *L) {
for (const string &param : parameters)
Lua::Push(L, param);
},
[&](lua_State *L) {
show_help = !lua_toboolean(L, -1);
})) {
return CR_FAILURE;
}
return show_help ? CR_WRONG_USAGE : CR_OK;
}
/////////////////////////////////////////////////////
// cycle logic
//
static void do_cycle(color_ostream &out)
{
// mark that we have recently run
cycle_timestamp = world->frame_counter;
// TODO
}
/////////////////////////////////////////////////////
// Lua API
//
static void get_bool_field(lua_State *L, int idx, const char *name, bool *dest) {
lua_getfield(L, idx, name);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return;
}
*dest = lua_toboolean(L, -1);
lua_pop(L, 1);
}
static void get_opts(lua_State *L, int idx, bool &dry_run, bool &zlevel) {
if (lua_gettop(L) < idx)
return;
get_bool_field(L, idx, "dry_run", &dry_run);
get_bool_field(L, idx, "zlevel", &zlevel);
}
static bool get_int_field(lua_State *L, int idx, const char *name, int16_t *dest) {
lua_getfield(L, idx, name);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return false;
}
*dest = lua_tointeger(L, -1);
lua_pop(L, 1);
return true;
}
static bool get_bounds(lua_State *L, int idx, df::coord &pos1, df::coord &pos2) {
return get_int_field(L, idx, "x1", &pos1.x) &&
get_int_field(L, idx, "y1", &pos1.y) &&
get_int_field(L, idx, "z1", &pos1.z) &&
get_int_field(L, idx, "x2", &pos2.x) &&
get_int_field(L, idx, "y2", &pos2.y) &&
get_int_field(L, idx, "z2", &pos2.z);
}
static df::burrow* get_burrow(lua_State *L, int idx) {
df::burrow *burrow = NULL;
if (lua_isuserdata(L, idx))
burrow = Lua::GetDFObject<df::burrow>(L, idx);
else if (lua_isstring(L, idx))
burrow = Burrows::findByName(luaL_checkstring(L, idx));
else if (lua_isinteger(L, idx))
burrow = df::burrow::find(luaL_checkinteger(L, idx));
return burrow;
}
static int burrow_tiles_clear(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
out = &Core::getInstance().getConsole();
DEBUG(status,*out).print("entering burrow_tiles_clear\n");
int32_t count = 0;
lua_pushnil(L); // first key
while (lua_next(L, 1)) {
df::burrow * burrow = get_burrow(L, -1);
if (burrow) {
count += burrow->block_x.size();
Burrows::clearTiles(burrow);
}
lua_pop(L, 1); // remove value, leave key
}
Lua::Push(L, count);
return 1;
}
static int burrow_tiles_set(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
out = &Core::getInstance().getConsole();
DEBUG(status,*out).print("entering burrow_tiles_set\n");
// TODO
return 0;
}
static int burrow_tiles_add(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
out = &Core::getInstance().getConsole();
DEBUG(status,*out).print("entering burrow_tiles_add\n");
// TODO
return 0;
}
static int burrow_tiles_remove(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
out = &Core::getInstance().getConsole();
DEBUG(status,*out).print("entering burrow_tiles_remove\n");
// TODO
return 0;
}
static int box_fill(lua_State *L, bool enable) {
df::coord pos_start, pos_end;
bool dry_run = false, zlevel = false;
df::burrow *burrow = get_burrow(L, 1);
if (!burrow) {
luaL_argerror(L, 1, "invalid burrow specifier or burrow not found");
return 0;
}
if (!get_bounds(L, 2, pos_start, pos_end)) {
luaL_argerror(L, 2, "invalid box bounds");
return 0;
}
get_opts(L, 3, dry_run, zlevel);
if (zlevel) {
pos_start.z = *window_z;
pos_end.z = *window_z;
}
int32_t count = 0;
for (int32_t z = pos_start.z; z <= pos_end.z; ++z) {
for (int32_t y = pos_start.y; y <= pos_end.y; ++y) {
for (int32_t x = pos_start.x; x <= pos_end.x; ++x) {
df::coord pos(x, y, z);
if (enable != Burrows::isAssignedTile(burrow, pos))
++count;
if (!dry_run)
Burrows::setAssignedTile(burrow, pos, enable);
}
}
}
Lua::Push(L, count);
return 1;
}
static int burrow_tiles_box_add(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
out = &Core::getInstance().getConsole();
DEBUG(status,*out).print("entering burrow_tiles_box_add\n");
return box_fill(L, true);
}
static int burrow_tiles_box_remove(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
out = &Core::getInstance().getConsole();
DEBUG(status,*out).print("entering burrow_tiles_box_remove\n");
return box_fill(L, false);
}
static int flood_fill(lua_State *L, bool enable) {
df::coord start_pos;
bool dry_run = false, zlevel = false;
df::burrow *burrow = get_burrow(L, 1);
if (!burrow) {
luaL_argerror(L, 1, "invalid burrow specifier or burrow not found");
return 0;
}
Lua::CheckDFAssign(L, &start_pos, 2);
get_opts(L, 3, dry_run, zlevel);
// record properties to match
df::tile_designation *start_des = Maps::getTileDesignation(start_pos);
if (!start_des) {
luaL_argerror(L, 2, "invalid starting coordinates");
return 0;
}
uint16_t start_walk = Maps::getWalkableGroup(start_pos);
int32_t count = 0;
std::stack<df::coord> flood;
flood.emplace(start_pos);
while(!flood.empty()) {
const df::coord pos = flood.top();
flood.pop();
df::tile_designation *des = Maps::getTileDesignation(pos);
if(!des ||
des->bits.outside != start_des->bits.outside ||
des->bits.hidden != start_des->bits.hidden)
{
continue;
}
if (!start_walk && Maps::getWalkableGroup(pos))
continue;
if (pos != start_pos && enable == Burrows::isAssignedTile(burrow, pos))
continue;
++count;
if (!dry_run)
Burrows::setAssignedTile(burrow, pos, enable);
// only go one tile outside of a walkability group
if (start_walk && start_walk != Maps::getWalkableGroup(pos))
continue;
flood.emplace(pos.x-1, pos.y-1, pos.z);
flood.emplace(pos.x, pos.y-1, pos.z);
flood.emplace(pos.x+1, pos.y-1, pos.z);
flood.emplace(pos.x-1, pos.y, pos.z);
flood.emplace(pos.x+1, pos.y, pos.z);
flood.emplace(pos.x-1, pos.y+1, pos.z);
flood.emplace(pos.x, pos.y+1, pos.z);
flood.emplace(pos.x+1, pos.y+1, pos.z);
if (!zlevel) {
df::coord pos_above(pos);
++pos_above.z;
df::tiletype *tt = Maps::getTileType(pos);
df::tiletype *tt_above = Maps::getTileType(pos_above);
if (tt_above && LowPassable(*tt_above))
flood.emplace(pos_above);
if (tt && LowPassable(*tt))
flood.emplace(pos.x, pos.y, pos.z-1);
}
}
Lua::Push(L, count);
return 1;
}
static int burrow_tiles_flood_add(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
out = &Core::getInstance().getConsole();
DEBUG(status,*out).print("entering burrow_tiles_flood_add\n");
return flood_fill(L, true);
}
static int burrow_tiles_flood_remove(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
out = &Core::getInstance().getConsole();
DEBUG(status,*out).print("entering burrow_tiles_flood_remove\n");
return flood_fill(L, false);
}
static int burrow_units_clear(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
out = &Core::getInstance().getConsole();
DEBUG(status,*out).print("entering burrow_units_clear\n");
// TODO
return 0;
}
static int burrow_units_set(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
out = &Core::getInstance().getConsole();
DEBUG(status,*out).print("entering burrow_units_set\n");
// TODO
return 0;
}
static int burrow_units_add(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
out = &Core::getInstance().getConsole();
DEBUG(status,*out).print("entering burrow_units_add\n");
// TODO
return 0;
}
static int burrow_units_remove(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
out = &Core::getInstance().getConsole();
DEBUG(status,*out).print("entering burrow_units_remove\n");
// TODO
return 0;
}
DFHACK_PLUGIN_LUA_COMMANDS {
DFHACK_LUA_COMMAND(burrow_tiles_clear),
DFHACK_LUA_COMMAND(burrow_tiles_set),
DFHACK_LUA_COMMAND(burrow_tiles_add),
DFHACK_LUA_COMMAND(burrow_tiles_remove),
DFHACK_LUA_COMMAND(burrow_tiles_box_add),
DFHACK_LUA_COMMAND(burrow_tiles_box_remove),
DFHACK_LUA_COMMAND(burrow_tiles_flood_add),
DFHACK_LUA_COMMAND(burrow_tiles_flood_remove),
DFHACK_LUA_COMMAND(burrow_units_clear),
DFHACK_LUA_COMMAND(burrow_units_set),
DFHACK_LUA_COMMAND(burrow_units_add),
DFHACK_LUA_COMMAND(burrow_units_remove),
DFHACK_LUA_END
};
/*
#include "Core.h"
#include "Console.h"
#include "Export.h"
@ -13,7 +506,6 @@
#include "modules/MapCache.h"
#include "modules/World.h"
#include "modules/Units.h"
#include "modules/Burrows.h"
#include "TileTypes.h"
#include "DataDefs.h"
@ -37,26 +529,16 @@ using namespace DFHack;
using namespace df::enums;
using namespace dfproto;
DFHACK_PLUGIN("burrows");
DFHACK_PLUGIN("burrow");
REQUIRE_GLOBAL(plotinfo);
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(gamemode);
/*
* Initialization.
*/
static command_result burrow(color_ostream &out, vector <string> & parameters);
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)
{
commands.push_back(
PluginCommand("burrow",
"Quick commands for burrow control.",
burrow));
if (Core::getInstance().isMapLoaded())
init_map(out);
@ -90,10 +572,6 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
return CR_OK;
}
/*
* State change tracking.
*/
static int name_burrow_id = -1;
static void handle_burrow_rename(color_ostream &out, df::burrow *burrow);
@ -214,10 +692,6 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
return CR_OK;
}
/*
* Config and processing
*/
static std::map<std::string,int> name_lookup;
static void parse_names()
@ -680,3 +1154,4 @@ static command_result burrow(color_ostream &out, vector <string> &parameters)
return CR_OK;
}
*/

@ -1,23 +1,241 @@
local _ENV = mkmodule('plugins.burrows')
local _ENV = mkmodule('plugins.burrow')
--[[
Native events:
Provided events:
* onBurrowRename(burrow)
* onDigComplete(job_type,pos,old_tiletype,new_tiletype)
Native functions:
--]]
* findByName(name) -> burrow
* copyUnits(dest,src,enable)
* copyTiles(dest,src,enable)
* setTilesByKeyword(dest,kwd,enable) -> success
local overlay = require('plugins.overlay')
local widgets = require('gui.widgets')
'enable' selects between add and remove modes
---------------------------------
-- BurrowDesignationOverlay
--
--]]
local selection_rect = df.global.selection_rect
local if_burrow = df.global.game.main_interface.burrow
local function is_choosing_area(pos)
return if_burrow.doing_rectangle and
selection_rect.start_x >= 0 and
(pos or dfhack.gui.getMousePos())
end
local function reset_selection_rect()
selection_rect.start_x = -30000
selection_rect.start_y = -30000
selection_rect.start_z = -30000
end
local function get_bounds(pos1, pos2)
pos1 = pos1 or dfhack.gui.getMousePos()
pos2 = pos2 or xyz2pos(selection_rect.start_x, selection_rect.start_y, selection_rect.start_z)
local bounds = {
x1=math.min(pos1.x, pos2.x),
x2=math.max(pos1.x, pos2.x),
y1=math.min(pos1.y, pos2.y),
y2=math.max(pos1.y, pos2.y),
z1=math.min(pos1.z, pos2.z),
z2=math.max(pos1.z, pos2.z),
}
-- clamp to map edges
bounds = {
x1=math.max(0, bounds.x1),
x2=math.min(df.global.world.map.x_count-1, bounds.x2),
y1=math.max(0, bounds.y1),
y2=math.min(df.global.world.map.y_count-1, bounds.y2),
z1=math.max(0, bounds.z1),
z2=math.min(df.global.world.map.z_count-1, bounds.z2),
}
return bounds
end
local function get_cur_area_dims()
local bounds = get_bounds()
return bounds.x2 - bounds.x1 + 1,
bounds.y2 - bounds.y1 + 1,
bounds.z2 - bounds.z1 + 1
end
BurrowDesignationOverlay = defclass(BurrowDesignationOverlay, overlay.OverlayWidget)
BurrowDesignationOverlay.ATTRS{
default_pos={x=6,y=9},
viewscreens='dwarfmode/Burrow/Paint',
default_enabled=true,
frame={w=54, h=1},
}
function BurrowDesignationOverlay:init()
self:addviews{
widgets.BannerPanel{
frame={t=0, l=0},
subviews={
widgets.Label{
frame={t=0, l=1},
text='Double-click to fill. Shift double-click to 3D fill.',
auto_width=true,
visible=function() return not is_choosing_area() end,
},
widgets.Label{
frame={t=0, l=1},
text_pen=COLOR_DARKGREY,
text={
'3D box select enabled: ',
{text=function() return ('%dx%dx%d'):format(get_cur_area_dims()) end},
},
visible=is_choosing_area,
},
},
},
}
end
local function flood_fill(pos, erasing, do_3d, painting_burrow)
local opts = {zlevel=not do_3d}
if erasing then
burrow_tiles_flood_remove(painting_burrow, pos, opts)
else
burrow_tiles_flood_add(painting_burrow, pos, opts)
end
reset_selection_rect()
end
local function box_fill(bounds, erasing, painting_burrow)
if bounds.z1 == bounds.z2 then return end
if erasing then
burrow_tiles_box_remove(painting_burrow, bounds)
else
burrow_tiles_box_add(painting_burrow, bounds)
end
end
function BurrowDesignationOverlay:onInput(keys)
if self:inputToSubviews(keys) then
return true
-- don't perform burrow modifications immediately -- painting_burrow may not yet
-- have been initialized. instead, allow clicks to go through so that vanilla
-- behavior is triggered before we modify the burrow further
elseif keys._MOUSE_L then
local pos = dfhack.gui.getMousePos()
if pos then
local now_ms = dfhack.getTickCount()
if not same_xyz(pos, self.saved_pos) then
self.last_click_ms = now_ms
self.saved_pos = pos
else
if now_ms - self.last_click_ms <= widgets.DOUBLE_CLICK_MS then
self.last_click_ms = 0
self.pending_fn = curry(flood_fill, pos, if_burrow.erasing, dfhack.internal.getModifiers().shift)
return
else
self.last_click_ms = now_ms
end
end
if is_choosing_area(pos) then
self.pending_fn = curry(box_fill, get_bounds(pos), if_burrow.erasing)
return
end
end
end
end
function BurrowDesignationOverlay:onRenderBody(dc)
BurrowDesignationOverlay.super.onRenderBody(self, dc)
local pending_fn = self.pending_fn
self.pending_fn = nil
if pending_fn and if_burrow.painting_burrow then
pending_fn(if_burrow.painting_burrow)
end
end
OVERLAY_WIDGETS = {
designation=BurrowDesignationOverlay,
}
rawset_default(_ENV, dfhack.burrows)
---------------------------------
-- commandline handling
--
local function set_add_remove(mode, which, params, opts)
local target_burrow = table.remove(params, 1)
return _ENV[('burrow_%s_%s'):format(mode, which)](target_burrow, params, opts)
end
local function tiles_box_add_remove(which, params, opts)
local target_burrow = table.remove(params, 1)
local pos1 = argparse.coords(params[1] or 'here', 'pos')
local pos2 = opts.cursor or argparse.coords(params[2] or 'here', 'pos')
local bounds = get_bounds(pos1, pos2)
return _ENV['burrow_tiles_box_'..which](target_burrow, bounds, opts)
end
local function tiles_flood_add_remove(which, params, opts)
local target_burrow = table.remove(params, 1)
local pos = opts.cursor or argparse.coords('here', 'pos')
return _ENV['burrow_tiles_flood_'..which](target_burrow, pos, opts)
end
local function run_command(mode, command, params, opts)
if mode == 'tiles' then
if command == 'clear' then
return burrow_tiles_clear(params, opts)
elseif command == 'set' or command == 'add' or command == 'remove' then
return set_add_remove('tiles', command, params, opts)
elseif command == 'box-add' or command == 'box-remove' then
return tiles_box_add_remove(command:sub(5), params, opts)
elseif command == 'flood-add' or command == 'flood-remove' then
return tiles_flood_add_remove(command:sub(7), params, opts)
else
return false
end
elseif mode == 'units' then
if command == 'clear' then
return burrow_units_clear(params)
elseif command == 'set' or command == 'add' or command == 'remove' then
return set_add_remove('units', command, params, opts)
else
return false
end
else
return false
end
end
function parse_commandline(...)
local args, opts = {...}, {}
if args[1] == 'help' then
return false
end
local positionals = argparse.processArgsGetopt(args, {
{'c', 'cursor', hasArg=true,
handler=function(optarg) opts.cursor = argparse.coords(optarg, 'cursor') end},
{'d', 'dry-run', handler=function() opts.dry_run = true end},
{'h', 'help', handler=function() opts.help = true end},
{'z', 'cur-zlevel', handler=function() opts.zlevel = true end},
})
if opts.help then
return false
end
local mode = table.remove(positionals, 1)
local command = table.remove(positionals, 1)
local ret = run_command(mode, command, positionals, opts)
if not ret then return false end
print(('%d %s affected'):format(ret, mode))
return true
end
return _ENV