basic commandline processing

and skeleton lua interface
develop
Myk Taylor 2023-10-28 19:26:15 -07:00
parent 3523975194
commit cf1e4e69ca
No known key found for this signature in database
3 changed files with 476 additions and 51 deletions

@ -20,12 +20,12 @@ Usage
enable burrow enable burrow
burrow tiles|units clear <target burrow> [<target burrow> ...] [<options>] burrow tiles|units clear <target burrow> [<target burrow> ...] [<options>]
burrow tiles|units set|add|remove <target burrow> <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 box-add|box-remove <target burrow> [<pos>] [<pos>] [<options>]
burrow tiles flood-add|flood-remove <target burrow> [<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 The burrows can be referenced by name or by the internal numeric burrow ID. If
referenced by name, all burrows that match the name (case sensitive) will be referenced by name, the first burrow that matches the name (case sensitive)
included. If a burrow name ends in ``+`` (to indicate that it should be 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 auto-expanded), the final ``+`` does not need to be specified on the
commandline. commandline.

@ -1,3 +1,361 @@
#include "Core.h"
#include "Debug.h"
#include "LuaTools.h"
#include "PluginManager.h"
#include "modules/Burrows.h"
#include "modules/Persistence.h"
#include "modules/World.h"
#include "df/burrow.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_opts(lua_State *L, int idx, bool &dry_run, bool &cur_zlevel) {
// TODO
}
static void get_bounds(lua_State *L, int idx, df::coord &pos1, df::coord &pos2) {
// TODO
}
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");
return 0;
}
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");
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");
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");
return 0;
}
static int box_fill(lua_State *L, bool enable) {
df::coord pos_start, pos_end;
bool dry_run = false, cur_zlevel = false;
df::burrow *burrow = NULL;
if (lua_isuserdata(L, 1))
burrow = Lua::GetDFObject<df::burrow>(L, 1);
else if (lua_isstring(L, 1))
burrow = Burrows::findByName(luaL_checkstring(L, 1));
else if (lua_isinteger(L, 1))
burrow = df::burrow::find(luaL_checkinteger(L, 1));
if (!burrow) {
luaL_argerror(L, 1, "invalid burrow specifier or burrow not found");
return 0;
}
get_bounds(L, 2, pos_start, pos_end);
get_opts(L, 3, dry_run, cur_zlevel);
if (cur_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 (!Burrows::isAssignedTile(burrow, pos))
++count;
if (!dry_run)
Burrows::setAssignedTile(burrow, pos, true);
}
}
}
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 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 0;
}
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 0;
}
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");
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");
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");
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");
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 "Core.h"
#include "Console.h" #include "Console.h"
#include "Export.h" #include "Export.h"
@ -13,7 +371,6 @@
#include "modules/MapCache.h" #include "modules/MapCache.h"
#include "modules/World.h" #include "modules/World.h"
#include "modules/Units.h" #include "modules/Units.h"
#include "modules/Burrows.h"
#include "TileTypes.h" #include "TileTypes.h"
#include "DataDefs.h" #include "DataDefs.h"
@ -37,26 +394,16 @@ using namespace DFHack;
using namespace df::enums; using namespace df::enums;
using namespace dfproto; using namespace dfproto;
DFHACK_PLUGIN("burrows"); DFHACK_PLUGIN("burrow");
REQUIRE_GLOBAL(plotinfo); REQUIRE_GLOBAL(plotinfo);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(gamemode); REQUIRE_GLOBAL(gamemode);
/*
* Initialization.
*/
static command_result burrow(color_ostream &out, vector <string> & parameters);
static void init_map(color_ostream &out); static void init_map(color_ostream &out);
static void deinit_map(color_ostream &out); static void deinit_map(color_ostream &out);
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) 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()) if (Core::getInstance().isMapLoaded())
init_map(out); init_map(out);
@ -90,10 +437,6 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
return CR_OK; return CR_OK;
} }
/*
* State change tracking.
*/
static int name_burrow_id = -1; static int name_burrow_id = -1;
static void handle_burrow_rename(color_ostream &out, df::burrow *burrow); static void handle_burrow_rename(color_ostream &out, df::burrow *burrow);
@ -214,10 +557,6 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
return CR_OK; return CR_OK;
} }
/*
* Config and processing
*/
static std::map<std::string,int> name_lookup; static std::map<std::string,int> name_lookup;
static void parse_names() static void parse_names()
@ -680,3 +1019,4 @@ static command_result burrow(color_ostream &out, vector <string> &parameters)
return CR_OK; return CR_OK;
} }
*/

@ -21,6 +21,10 @@ local _ENV = mkmodule('plugins.burrow')
local overlay = require('plugins.overlay') local overlay = require('plugins.overlay')
local widgets = require('gui.widgets') local widgets = require('gui.widgets')
---------------------------------
-- BurrowDesignationOverlay
--
local selection_rect = df.global.selection_rect local selection_rect = df.global.selection_rect
local if_burrow = df.global.game.main_interface.burrow local if_burrow = df.global.game.main_interface.burrow
@ -36,15 +40,16 @@ local function reset_selection_rect()
selection_rect.start_z = -30000 selection_rect.start_z = -30000
end end
local function get_bounds(pos) local function get_bounds(pos1, pos2)
pos = pos or dfhack.gui.getMousePos() pos1 = pos1 or dfhack.gui.getMousePos()
pos2 = pos2 or xyz2pos(selection_rect.start_x, selection_rect.start_y, selection_rect.start_z)
local bounds = { local bounds = {
x1=math.min(selection_rect.start_x, pos.x), x1=math.min(pos1.x, pos2.x),
x2=math.max(selection_rect.start_x, pos.x), x2=math.max(pos1.x, pos2.x),
y1=math.min(selection_rect.start_y, pos.y), y1=math.min(pos1.y, pos2.y),
y2=math.max(selection_rect.start_y, pos.y), y2=math.max(pos1.y, pos2.y),
z1=math.min(selection_rect.start_z, pos.z), z1=math.min(pos1.z, pos2.z),
z2=math.max(selection_rect.start_z, pos.z), z2=math.max(pos1.z, pos2.z),
} }
-- clamp to map edges -- clamp to map edges
@ -69,10 +74,10 @@ end
BurrowDesignationOverlay = defclass(BurrowDesignationOverlay, overlay.OverlayWidget) BurrowDesignationOverlay = defclass(BurrowDesignationOverlay, overlay.OverlayWidget)
BurrowDesignationOverlay.ATTRS{ BurrowDesignationOverlay.ATTRS{
default_pos={x=6,y=8}, default_pos={x=6,y=9},
viewscreens='dwarfmode/Burrow/Paint', viewscreens='dwarfmode/Burrow/Paint',
default_enabled=true, default_enabled=true,
frame={w=30, h=2}, frame={w=54, h=1},
} }
function BurrowDesignationOverlay:init() function BurrowDesignationOverlay:init()
@ -82,39 +87,41 @@ function BurrowDesignationOverlay:init()
subviews={ subviews={
widgets.Label{ widgets.Label{
frame={t=0, l=1}, frame={t=0, l=1},
text='Double-click to flood fill.', text='Double-click to fill.',
auto_width=true,
}, },
},
},
widgets.BannerPanel{
frame={t=1, l=0},
subviews={
widgets.Label{ widgets.Label{
frame={t=0, l=1}, frame={t=0, l=25},
text_pen=COLOR_DARKGREY, text_pen=COLOR_DARKGREY,
text={ text={
'Selected area: ', 'Selected area: ',
{text=function() return ('%dx%dx%d'):format(get_cur_area_dims()) end}, {text=function() return ('%dx%dx%d'):format(get_cur_area_dims()) end},
}, },
visible=is_choosing_area,
}, },
}, },
visible=is_choosing_area,
}, },
} }
end end
local function flood_fill(pos, painting_burrow) local function flood_fill(pos, erasing, painting_burrow)
print('flood fill, erasing:', if_burrow.erasing) if erasing then
print('origin pos:') burrow_tiles_flood_remove(painting_burrow, pos)
printall(pos) else
burrow_tiles_flood_add(painting_burrow, pos)
end
reset_selection_rect() reset_selection_rect()
end end
local function box_fill(bounds, painting_burrow) local function box_fill(bounds, erasing, painting_burrow)
print('box fill, erasing:', if_burrow.erasing)
print('bounds:')
printall(bounds)
if bounds.z1 == bounds.z2 then return end if bounds.z1 == bounds.z2 then return end
local pos1 = xyz2pos(bounds.x1, bounds.y1, bounds.z1)
local pos2 = xyz2pos(bounds.x2, bounds.y2, bounds.z2)
if erasing then
burrow_tiles_box_remove(painting_burrow, pos1, pos2)
else
burrow_tiles_box_add(painting_burrow, pos1, pos2)
end
end end
function BurrowDesignationOverlay:onInput(keys) function BurrowDesignationOverlay:onInput(keys)
@ -133,14 +140,14 @@ function BurrowDesignationOverlay:onInput(keys)
else else
if now_ms - self.last_click_ms <= widgets.DOUBLE_CLICK_MS then if now_ms - self.last_click_ms <= widgets.DOUBLE_CLICK_MS then
self.last_click_ms = 0 self.last_click_ms = 0
self.pending_fn = curry(flood_fill, pos) self.pending_fn = curry(flood_fill, pos, if_burrow.erasing)
return return
else else
self.last_click_ms = now_ms self.last_click_ms = now_ms
end end
end end
if is_choosing_area(pos) then if is_choosing_area(pos) then
self.pending_fn = curry(box_fill, get_bounds(pos)) self.pending_fn = curry(box_fill, get_bounds(pos), if_burrow.erasing)
return return
end end
end end
@ -162,4 +169,82 @@ OVERLAY_WIDGETS = {
rawset_default(_ENV, dfhack.burrows) 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 return _ENV