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
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 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, all burrows that match the name (case sensitive) will be
included. If a burrow name ends in ``+`` (to indicate that it should be
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.

@ -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 "Console.h"
#include "Export.h"
@ -13,7 +371,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 +394,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 +437,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 +557,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 +1019,4 @@ static command_result burrow(color_ostream &out, vector <string> &parameters)
return CR_OK;
}
*/

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