2023-10-28 20:26:15 -06:00
|
|
|
#include "Core.h"
|
|
|
|
#include "Debug.h"
|
|
|
|
#include "LuaTools.h"
|
|
|
|
#include "PluginManager.h"
|
2023-10-29 17:34:18 -06:00
|
|
|
#include "TileTypes.h"
|
2023-10-28 20:26:15 -06:00
|
|
|
|
|
|
|
#include "modules/Burrows.h"
|
2023-11-02 13:44:37 -06:00
|
|
|
#include "modules/EventManager.h"
|
|
|
|
#include "modules/Job.h"
|
2023-10-28 20:26:15 -06:00
|
|
|
#include "modules/Persistence.h"
|
|
|
|
#include "modules/World.h"
|
|
|
|
|
2023-10-31 17:56:45 -06:00
|
|
|
#include "df/block_burrow.h"
|
2023-10-28 20:26:15 -06:00
|
|
|
#include "df/burrow.h"
|
2023-10-31 19:54:38 -06:00
|
|
|
#include "df/map_block.h"
|
2023-11-02 13:44:37 -06:00
|
|
|
#include "df/plotinfost.h"
|
2023-10-29 17:34:18 -06:00
|
|
|
#include "df/tile_designation.h"
|
2023-10-31 17:56:45 -06:00
|
|
|
#include "df/unit.h"
|
2023-10-28 20:26:15 -06:00
|
|
|
#include "df/world.h"
|
|
|
|
|
|
|
|
using std::vector;
|
|
|
|
using std::string;
|
|
|
|
using namespace DFHack;
|
|
|
|
|
|
|
|
DFHACK_PLUGIN("burrow");
|
|
|
|
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
|
|
|
|
2023-11-02 13:44:37 -06:00
|
|
|
REQUIRE_GLOBAL(plotinfo);
|
2023-10-28 20:26:15 -06:00
|
|
|
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
|
2023-11-02 13:44:37 -06:00
|
|
|
DBG_DECLARE(burrow, event, DebugCategory::LINFO);
|
2023-10-28 20:26:15 -06:00
|
|
|
}
|
|
|
|
|
2023-11-02 13:44:37 -06:00
|
|
|
static std::unordered_map<df::coord, df::tiletype> active_dig_jobs;
|
2023-10-28 20:26:15 -06:00
|
|
|
|
|
|
|
static command_result do_command(color_ostream &out, vector<string> ¶meters);
|
2023-11-02 13:44:37 -06:00
|
|
|
static void init_diggers(color_ostream& out);
|
|
|
|
static void jobStartedHandler(color_ostream& out, void* ptr);
|
|
|
|
static void jobCompletedHandler(color_ostream& out, void* ptr);
|
2023-10-28 20:26:15 -06:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-11-02 13:44:37 -06:00
|
|
|
static void reset() {
|
|
|
|
active_dig_jobs.clear();
|
|
|
|
}
|
2023-10-28 20:26:15 -06:00
|
|
|
|
2023-11-02 13:44:37 -06:00
|
|
|
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
|
2023-10-28 20:26:15 -06:00
|
|
|
if (enable != is_enabled) {
|
|
|
|
is_enabled = enable;
|
2023-11-02 13:44:37 -06:00
|
|
|
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);
|
2023-11-04 20:10:19 -06:00
|
|
|
} else {
|
|
|
|
EventManager::unregisterAll(plugin_self);
|
2023-11-02 13:44:37 -06:00
|
|
|
}
|
2023-10-28 20:26:15 -06:00
|
|
|
}
|
|
|
|
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);
|
2023-11-02 13:44:37 -06:00
|
|
|
reset();
|
2023-10-28 20:26:15 -06:00
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
|
2023-11-02 13:44:37 -06:00
|
|
|
if (event == DFHack::SC_WORLD_UNLOADED)
|
|
|
|
reset();
|
2023-10-28 20:26:15 -06:00
|
|
|
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> ¶meters) {
|
|
|
|
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 ¶m : 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
2023-11-02 13:44:37 -06:00
|
|
|
// listener logic
|
2023-10-28 20:26:15 -06:00
|
|
|
//
|
|
|
|
|
2023-11-02 13:44:37 -06:00
|
|
|
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)
|
2023-10-28 20:26:15 -06:00
|
|
|
{
|
2023-11-02 13:44:37 -06:00
|
|
|
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) {
|
2023-11-03 10:21:14 -06:00
|
|
|
if (!isWalkable(tt) && tileShape(tt) != tiletype_shape::RAMP_TOP)
|
2023-11-02 13:44:37 -06:00
|
|
|
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);
|
2023-10-28 20:26:15 -06:00
|
|
|
|
2023-11-02 13:44:37 -06:00
|
|
|
active_dig_jobs.erase(pos);
|
2023-10-28 20:26:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
|
|
// Lua API
|
|
|
|
//
|
|
|
|
|
2023-10-29 15:09:42 -06:00
|
|
|
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);
|
2023-10-28 20:26:15 -06:00
|
|
|
}
|
|
|
|
|
2023-10-31 19:54:38 -06:00
|
|
|
static void get_opts(lua_State *L, int idx, bool &zlevel) {
|
2023-10-29 15:09:42 -06:00
|
|
|
if (lua_gettop(L) < idx)
|
|
|
|
return;
|
|
|
|
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);
|
2023-10-28 20:26:15 -06:00
|
|
|
}
|
|
|
|
|
2023-10-30 17:27:14 -06:00
|
|
|
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))
|
2023-11-02 14:55:08 -06:00
|
|
|
burrow = Burrows::findByName(luaL_checkstring(L, idx), true);
|
2023-10-30 17:27:14 -06:00
|
|
|
else if (lua_isinteger(L, idx))
|
|
|
|
burrow = df::burrow::find(luaL_checkinteger(L, idx));
|
|
|
|
return burrow;
|
|
|
|
}
|
|
|
|
|
2023-10-31 19:54:38 -06:00
|
|
|
static void copyTiles(df::burrow *target, df::burrow *source, bool enable) {
|
2023-10-31 17:56:45 -06:00
|
|
|
CHECK_NULL_POINTER(target);
|
|
|
|
CHECK_NULL_POINTER(source);
|
|
|
|
|
|
|
|
if (source == target) {
|
|
|
|
if (!enable)
|
|
|
|
Burrows::clearTiles(target);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
vector<df::map_block*> pvec;
|
|
|
|
Burrows::listBlocks(&pvec, source);
|
|
|
|
|
|
|
|
for (auto block : pvec) {
|
|
|
|
auto smask = Burrows::getBlockMask(source, block);
|
|
|
|
if (!smask)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
auto tmask = Burrows::getBlockMask(target, block, enable);
|
|
|
|
if (!tmask)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (enable) {
|
|
|
|
for (int j = 0; j < 16; j++)
|
|
|
|
tmask->tile_bitmask[j] |= smask->tile_bitmask[j];
|
|
|
|
} else {
|
|
|
|
for (int j = 0; j < 16; j++)
|
|
|
|
tmask->tile_bitmask[j] &= ~smask->tile_bitmask[j];
|
|
|
|
|
|
|
|
if (!tmask->has_assignments())
|
|
|
|
Burrows::deleteBlockMask(target, block, tmask);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-31 19:54:38 -06:00
|
|
|
static void setTilesByDesignation(df::burrow *target, df::tile_designation d_mask,
|
|
|
|
df::tile_designation d_value, bool enable) {
|
|
|
|
CHECK_NULL_POINTER(target);
|
|
|
|
|
|
|
|
auto &blocks = world->map.map_blocks;
|
|
|
|
|
|
|
|
for (auto block : blocks) {
|
|
|
|
df::block_burrow *mask = NULL;
|
|
|
|
|
|
|
|
for (int x = 0; x < 16; x++) {
|
|
|
|
for (int y = 0; y < 16; y++) {
|
|
|
|
if ((block->designation[x][y].whole & d_mask.whole) != d_value.whole)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!mask)
|
|
|
|
mask = Burrows::getBlockMask(target, block, enable);
|
|
|
|
if (!mask)
|
|
|
|
goto next_block;
|
|
|
|
|
|
|
|
mask->setassignment(x, y, enable);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mask && !enable && !mask->has_assignments())
|
|
|
|
Burrows::deleteBlockMask(target, block, mask);
|
|
|
|
|
|
|
|
next_block:;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable) {
|
|
|
|
CHECK_NULL_POINTER(target);
|
|
|
|
|
|
|
|
df::tile_designation mask;
|
|
|
|
df::tile_designation value;
|
|
|
|
|
|
|
|
if (name == "ABOVE_GROUND")
|
|
|
|
mask.bits.subterranean = true;
|
|
|
|
else if (name == "SUBTERRANEAN")
|
|
|
|
mask.bits.subterranean = value.bits.subterranean = true;
|
|
|
|
else if (name == "LIGHT")
|
|
|
|
mask.bits.light = value.bits.light = true;
|
|
|
|
else if (name == "DARK")
|
|
|
|
mask.bits.light = true;
|
|
|
|
else if (name == "OUTSIDE")
|
|
|
|
mask.bits.outside = value.bits.outside = true;
|
|
|
|
else if (name == "INSIDE")
|
|
|
|
mask.bits.outside = true;
|
|
|
|
else if (name == "HIDDEN")
|
|
|
|
mask.bits.hidden = value.bits.hidden = true;
|
|
|
|
else if (name == "REVEALED")
|
|
|
|
mask.bits.hidden = true;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
|
|
|
|
setTilesByDesignation(target, mask, value, enable);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void copyUnits(df::burrow *target, df::burrow *source, bool enable) {
|
|
|
|
CHECK_NULL_POINTER(target);
|
|
|
|
CHECK_NULL_POINTER(source);
|
|
|
|
|
|
|
|
if (source == target) {
|
|
|
|
if (!enable)
|
|
|
|
Burrows::clearUnits(target);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < source->units.size(); i++) {
|
|
|
|
if (auto unit = df::unit::find(source->units[i]))
|
|
|
|
Burrows::setAssignedUnit(target, unit, enable);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
lua_pushnil(L); // first key
|
|
|
|
while (lua_next(L, 1)) {
|
|
|
|
df::burrow * burrow = get_burrow(L, -1);
|
|
|
|
if (burrow)
|
|
|
|
Burrows::clearTiles(burrow);
|
|
|
|
lua_pop(L, 1); // remove value, leave key
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tiles_set_add_remove(lua_State *L, bool do_set, bool enable) {
|
|
|
|
df::burrow *target = get_burrow(L, 1);
|
|
|
|
if (!target) {
|
|
|
|
luaL_argerror(L, 1, "invalid burrow specifier or burrow not found");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (do_set)
|
|
|
|
Burrows::clearTiles(target);
|
|
|
|
|
|
|
|
lua_pushnil(L); // first key
|
|
|
|
while (lua_next(L, 2)) {
|
|
|
|
if (!lua_isstring(L, -1) || !setTilesByKeyword(target, luaL_checkstring(L, -1), enable)) {
|
|
|
|
if (auto burrow = get_burrow(L, -1))
|
|
|
|
copyTiles(target, burrow, enable);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1); // remove value, leave key
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-28 20:26:15 -06:00
|
|
|
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");
|
2023-10-31 19:54:38 -06:00
|
|
|
tiles_set_add_remove(L, true, true);
|
2023-10-28 20:26:15 -06:00
|
|
|
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");
|
2023-10-31 19:54:38 -06:00
|
|
|
tiles_set_add_remove(L, false, true);
|
2023-10-28 20:26:15 -06:00
|
|
|
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");
|
2023-10-31 19:54:38 -06:00
|
|
|
tiles_set_add_remove(L, false, false);
|
2023-10-28 20:26:15 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-10-31 19:54:38 -06:00
|
|
|
static void box_fill(lua_State *L, bool enable) {
|
2023-10-29 17:34:18 -06:00
|
|
|
df::burrow *burrow = get_burrow(L, 1);
|
2023-10-28 20:26:15 -06:00
|
|
|
if (!burrow) {
|
|
|
|
luaL_argerror(L, 1, "invalid burrow specifier or burrow not found");
|
2023-10-31 19:54:38 -06:00
|
|
|
return;
|
2023-10-28 20:26:15 -06:00
|
|
|
}
|
|
|
|
|
2023-10-31 19:54:38 -06:00
|
|
|
df::coord pos_start, pos_end;
|
2023-10-29 15:09:42 -06:00
|
|
|
if (!get_bounds(L, 2, pos_start, pos_end)) {
|
|
|
|
luaL_argerror(L, 2, "invalid box bounds");
|
2023-10-31 19:54:38 -06:00
|
|
|
return;
|
2023-10-28 20:26:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2023-10-31 19:54:38 -06:00
|
|
|
Burrows::setAssignedTile(burrow, pos, enable);
|
2023-10-28 20:26:15 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
2023-10-31 19:54:38 -06:00
|
|
|
box_fill(L, true);
|
|
|
|
return 0;
|
2023-10-28 20:26:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
2023-10-31 19:54:38 -06:00
|
|
|
box_fill(L, false);
|
|
|
|
return 0;
|
2023-10-28 20:26:15 -06:00
|
|
|
}
|
|
|
|
|
2023-11-01 13:54:26 -06:00
|
|
|
// ramp tops inherit walkability group of the tile below
|
|
|
|
static uint16_t get_walk_group(const df::coord & pos) {
|
2023-11-09 16:51:44 -07:00
|
|
|
df::tile_designation *des = Maps::getTileDesignation(pos);
|
|
|
|
if (!des || des->bits.hidden)
|
|
|
|
return 0;
|
2023-11-01 13:54:26 -06:00
|
|
|
uint16_t walk = Maps::getWalkableGroup(pos);
|
|
|
|
if (walk)
|
|
|
|
return walk;
|
|
|
|
if (auto tt = Maps::getTileType(pos)) {
|
|
|
|
if (tileShape(*tt) == df::tiletype_shape::RAMP_TOP) {
|
|
|
|
df::coord pos_below(pos);
|
|
|
|
--pos_below.z;
|
|
|
|
walk = Maps::getWalkableGroup(pos_below);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return walk;
|
|
|
|
}
|
|
|
|
|
2023-11-12 13:37:13 -07:00
|
|
|
static bool is_tree(const df::tiletype *tt) {
|
|
|
|
return tileMaterial(*tt) == tiletype_material::TREE ||
|
|
|
|
tileMaterial(*tt) == tiletype_material::MUSHROOM;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the outside tile flag is set
|
|
|
|
// or it's light and it's a tree
|
|
|
|
// or there are just outside or light tree tiles above it
|
|
|
|
static bool is_outside(const df::coord & pos, const df::tile_designation *des) {
|
|
|
|
if (!des)
|
|
|
|
return false;
|
|
|
|
if (des->bits.outside)
|
|
|
|
return true;
|
|
|
|
if (!des->bits.light)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
df::coord pos_above = pos + df::coord(0, 0, 1);
|
|
|
|
df::tile_designation *des_above = Maps::getTileDesignation(pos_above);
|
|
|
|
df::tiletype *tt_above = Maps::getTileType(pos_above);
|
|
|
|
if (!des_above || !tt_above || (!is_tree(tt_above) && !LowPassable(*tt_above)))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return is_outside(pos_above, des_above);
|
|
|
|
}
|
|
|
|
|
2023-10-31 19:54:38 -06:00
|
|
|
static void flood_fill(lua_State *L, bool enable) {
|
2023-10-29 17:34:18 -06:00
|
|
|
df::coord start_pos;
|
2023-10-31 19:54:38 -06:00
|
|
|
bool zlevel = false;
|
2023-10-29 17:34:18 -06:00
|
|
|
|
|
|
|
df::burrow *burrow = get_burrow(L, 1);
|
|
|
|
if (!burrow) {
|
|
|
|
luaL_argerror(L, 1, "invalid burrow specifier or burrow not found");
|
2023-10-31 19:54:38 -06:00
|
|
|
return;
|
2023-10-29 17:34:18 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
Lua::CheckDFAssign(L, &start_pos, 2);
|
2023-10-31 19:54:38 -06:00
|
|
|
get_opts(L, 3, zlevel);
|
2023-10-29 17:34:18 -06:00
|
|
|
|
|
|
|
df::tile_designation *start_des = Maps::getTileDesignation(start_pos);
|
|
|
|
if (!start_des) {
|
|
|
|
luaL_argerror(L, 2, "invalid starting coordinates");
|
2023-10-31 19:54:38 -06:00
|
|
|
return;
|
2023-10-29 17:34:18 -06:00
|
|
|
}
|
2023-11-12 13:37:13 -07:00
|
|
|
bool start_outside = is_outside(start_pos, start_des);
|
|
|
|
bool start_hidden = start_des->bits.hidden;
|
2023-10-29 17:34:18 -06:00
|
|
|
uint16_t start_walk = Maps::getWalkableGroup(start_pos);
|
2023-11-12 13:37:13 -07:00
|
|
|
DEBUG(status).print("starting pos: (%d,%d,%d); outside: %d; hidden: %d\n",
|
|
|
|
start_pos.x, start_pos.y, start_pos.z, start_outside, start_hidden);
|
2023-10-29 17:34:18 -06:00
|
|
|
|
|
|
|
std::stack<df::coord> flood;
|
|
|
|
flood.emplace(start_pos);
|
|
|
|
|
|
|
|
while(!flood.empty()) {
|
|
|
|
const df::coord pos = flood.top();
|
|
|
|
flood.pop();
|
|
|
|
|
2023-11-12 13:37:13 -07:00
|
|
|
TRACE(status).print("pos: (%d,%d,%d)\n", pos.x, pos.y, pos.z);
|
|
|
|
|
2023-10-29 17:34:18 -06:00
|
|
|
df::tile_designation *des = Maps::getTileDesignation(pos);
|
2023-11-09 16:51:44 -07:00
|
|
|
if (!des ||
|
2023-11-12 13:37:13 -07:00
|
|
|
is_outside(pos, des) != start_outside ||
|
|
|
|
des->bits.hidden != start_hidden)
|
2023-10-29 17:34:18 -06:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-11-01 13:54:26 -06:00
|
|
|
uint16_t walk = get_walk_group(pos);
|
|
|
|
if (!start_walk && walk)
|
2023-10-29 17:34:18 -06:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if (pos != start_pos && enable == Burrows::isAssignedTile(burrow, pos))
|
|
|
|
continue;
|
|
|
|
|
2023-10-31 19:54:38 -06:00
|
|
|
Burrows::setAssignedTile(burrow, pos, enable);
|
2023-10-29 17:34:18 -06:00
|
|
|
|
2023-11-12 13:37:13 -07:00
|
|
|
// only go one tile outside of a walkability group (trees don't count)
|
|
|
|
df::tiletype *tt = Maps::getTileType(pos);
|
|
|
|
if (start_walk && start_walk != walk && tt && !is_tree(tt))
|
2023-10-29 17:34:18 -06:00
|
|
|
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) {
|
2023-11-12 13:37:13 -07:00
|
|
|
df::coord pos_above = pos + df::coord(0, 0, 1);
|
2023-10-29 17:34:18 -06:00
|
|
|
df::tiletype *tt_above = Maps::getTileType(pos_above);
|
2023-11-12 13:37:13 -07:00
|
|
|
if (tt_above) {
|
|
|
|
uint16_t walk_above = get_walk_group(pos_above);
|
|
|
|
if (start_walk == walk_above)
|
|
|
|
flood.emplace(pos_above);
|
|
|
|
}
|
|
|
|
|
|
|
|
df::coord pos_below = pos + df::coord(0, 0, -1);
|
|
|
|
df::tiletype *tt_below = Maps::getTileType(pos_below);
|
|
|
|
if (tt_below) {
|
|
|
|
uint16_t walk_below = get_walk_group(pos_below);
|
|
|
|
if (start_walk == walk_below)
|
|
|
|
flood.emplace(pos_below);
|
|
|
|
}
|
2023-10-29 17:34:18 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-28 20:26:15 -06:00
|
|
|
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");
|
2023-10-31 19:54:38 -06:00
|
|
|
flood_fill(L, true);
|
|
|
|
return 0;
|
2023-10-28 20:26:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
2023-10-31 19:54:38 -06:00
|
|
|
flood_fill(L, false);
|
|
|
|
return 0;
|
2023-10-28 20:26:15 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
2023-10-31 19:54:38 -06:00
|
|
|
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->units.size();
|
|
|
|
Burrows::clearUnits(burrow);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1); // remove value, leave key
|
|
|
|
}
|
2023-10-31 17:56:45 -06:00
|
|
|
|
2023-10-31 19:54:38 -06:00
|
|
|
Lua::Push(L, count);
|
|
|
|
return 1;
|
|
|
|
}
|
2023-10-31 17:56:45 -06:00
|
|
|
|
2023-10-31 19:54:38 -06:00
|
|
|
static void units_set_add_remove(lua_State *L, bool do_set, bool enable) {
|
|
|
|
df::burrow *target = get_burrow(L, 1);
|
|
|
|
if (!target) {
|
|
|
|
luaL_argerror(L, 1, "invalid burrow specifier or burrow not found");
|
2023-10-31 17:56:45 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-10-31 19:54:38 -06:00
|
|
|
if (do_set)
|
|
|
|
Burrows::clearUnits(target);
|
2023-10-31 17:56:45 -06:00
|
|
|
|
2023-10-31 19:54:38 -06:00
|
|
|
lua_pushnil(L); // first key
|
|
|
|
while (lua_next(L, 2)) {
|
|
|
|
if (auto burrow = get_burrow(L, -1))
|
|
|
|
copyUnits(target, burrow, enable);
|
|
|
|
lua_pop(L, 1); // remove value, leave key
|
2023-10-31 17:56:45 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-28 20:26:15 -06:00
|
|
|
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");
|
2023-10-31 19:54:38 -06:00
|
|
|
units_set_add_remove(L, true, true);
|
2023-10-28 20:26:15 -06:00
|
|
|
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");
|
2023-10-31 19:54:38 -06:00
|
|
|
units_set_add_remove(L, false, true);
|
2023-10-28 20:26:15 -06:00
|
|
|
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");
|
2023-10-31 19:54:38 -06:00
|
|
|
units_set_add_remove(L, false, false);
|
2023-10-28 20:26:15 -06:00
|
|
|
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
|
|
|
|
};
|