Implements plugin: channel-safely v1.1a

develop
Josh Cooper 2022-11-21 12:39:26 -08:00
parent 9959ef1b36
commit 8a0999ffdc
12 changed files with 105 additions and 174 deletions

@ -2,12 +2,12 @@ channel-safely
==============
.. dfhack-tool::
:summary: Auto-manage channel designations to keep dwarves safe
:summary: Auto-manage channel designations to keep dwarves safe.
:tags: fort auto
Multi-level channel projects can be dangerous, and managing the safety of your
dwarves throughout the completion of such projects can be difficult and time
consuming. This plugin keeps your dwarves safe (while channeling) so you don't
consuming. This plugin keeps your dwarves safe (at least while channeling) so you don't
have to. Now you can focus on designing your dwarven cities with the deep chasms
they were meant to have.
@ -18,7 +18,7 @@ Usage
enable channel-safely
channel-safely set <setting> <value>
channel-safely enable|disable <feature>
channel-safely runonce
channel-safely <command>
When enabled the map will be scanned for channel designations which will be grouped
together based on adjacency and z-level. These groups will then be analyzed for safety
@ -38,13 +38,20 @@ Examples
``channel-safely disable require-vision``
Allows the plugin to read all tiles, including the ones your dwarves know nothing about.
``channel-safely enable monitor-active``
``channel-safely enable monitor``
Enables monitoring active channel digging jobs. Meaning that if another unit it present
or the tile below becomes open space the job will be paused or canceled (respectively).
``channel-safely set ignore-threshold 3``
Configures the plugin to ignore designations equal to or above priority 3 designations.
Commands
--------
:runonce: Run the safety procedures once to set the marker mode of designations.
:rebuild: Rebuild the designation group data. Intended for to be used in the event
the marker mode isn't being set correctly (mostly for debugging).
Features
--------

@ -2,7 +2,6 @@ project(channel-safely)
include_directories(include)
SET(SOURCES
channel-jobs.cpp
channel-groups.cpp
channel-manager.cpp
channel-safely-plugin.cpp)

@ -6,7 +6,18 @@
#include <random>
// iterates the DF job list and adds channel jobs to the `jobs` container
void ChannelJobs::load_channel_jobs() {
locations.clear();
df::job_list_link* node = df::global::world->jobs.list.next;
while (node) {
df::job* job = node->item;
node = node->next;
if (is_dig_job(job)) {
locations.emplace(job->pos);
}
}
}
// adds map_pos to a group if an adjacent one exists, or creates one if none exist... if multiple exist they're merged into the first found
void ChannelGroups::add(const df::coord &map_pos) {
@ -148,6 +159,7 @@ void ChannelGroups::scan() {
// the tile, check if it has a channel designation
df::coord map_pos((bx * 16) + lx, (by * 16) + ly, z);
if (TileCache::Get().hasChanged(map_pos, block->tiletype[lx][ly])) {
TileCache::Get().uncache(map_pos);
remove(map_pos);
if (jobs.count(map_pos)) {
jobs.erase(map_pos);
@ -165,6 +177,8 @@ void ChannelGroups::scan() {
}
TRACE(groups).print(" adding (" COORD ")\n", COORDARGS(map_pos));
add(map_pos);
} else if (groups_map.count(map_pos)) {
remove(map_pos);
}
}
}
@ -252,21 +266,25 @@ size_t ChannelGroups::count(const df::coord &map_pos) const {
// prints debug info about the groups stored, and their members
void ChannelGroups::debug_groups() {
int idx = 0;
TRACE(groups).print(" debugging group data\n");
for (auto &group : groups) {
TRACE(groups).print(" group %d (size: %zu)\n", idx, group.size());
for (auto &pos : group) {
TRACE(groups).print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z);
if (DFHack::debug_groups.isEnabled(DebugCategory::LTRACE)) {
int idx = 0;
TRACE(groups).print(" debugging group data\n");
for (auto &group: groups) {
TRACE(groups).print(" group %d (size: %zu)\n", idx, group.size());
for (auto &pos: group) {
TRACE(groups).print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z);
}
idx++;
}
idx++;
}
}
// prints debug info group mappings
void ChannelGroups::debug_map() {
INFO(groups).print("Group Mappings: %zu\n", groups_map.size());
for (auto &pair : groups_map) {
DEBUG(groups).print(" map[" COORD "] = %d\n",COORDARGS(pair.first), pair.second);
if (DFHack::debug_groups.isEnabled(DebugCategory::LDEBUG)) {
INFO(groups).print("Group Mappings: %zu\n", groups_map.size());
for (auto &pair: groups_map) {
DEBUG(groups).print(" map[" COORD "] = %d\n", COORDARGS(pair.first), pair.second);
}
}
}

@ -1,46 +0,0 @@
#include <channel-jobs.h>
#include <inlines.h>
#include <df/world.h>
#include <df/job.h>
// iterates the DF job list and adds channel jobs to the `jobs` container
void ChannelJobs::load_channel_jobs() {
jobs.clear();
df::job_list_link* node = df::global::world->jobs.list.next;
while (node) {
df::job* job = node->item;
node = node->next;
if (is_dig_job(job)) {
jobs.emplace(job->pos);
}
}
}
// clears the container
void ChannelJobs::clear() {
jobs.clear();
}
// finds and erases a job corresponding to a map position, then returns the iterator following the element removed
std::set<df::coord>::iterator ChannelJobs::erase(const df::coord &map_pos) {
auto iter = jobs.find(map_pos);
if (iter != jobs.end()) {
return jobs.erase(iter);
}
return iter;
}
// finds a job corresponding to a map position if one exists
std::set<df::coord>::const_iterator ChannelJobs::find(const df::coord &map_pos) const {
return jobs.find(map_pos);
}
// returns an iterator to the first element stored
std::set<df::coord>::const_iterator ChannelJobs::begin() const {
return jobs.begin();
}
// returns an iterator to after the last element stored
std::set<df::coord>::const_iterator ChannelJobs::end() const {
return jobs.end();
}

@ -5,11 +5,6 @@
#include <modules/EventManager.h> //hash function for df::coord
#include <df/block_square_event_designation_priorityst.h>
/**
blocks[48][96][135]: <map_block: 0x7fff8e587200>
blocks[48][96][135].default_liquid.hidden: false
blocks[48][96][135].designation[10][0].hidden: false
* */
// sets mark flags as necessary, for all designations
void ChannelManager::manage_groups() {

@ -93,7 +93,6 @@ PersistentDataItem psetting;
PersistentDataItem pfeature;
const std::string FCONFIG_KEY = std::string(plugin_name) + "/feature";
const std::string SCONFIG_KEY = std::string(plugin_name) + "/setting";
//std::unordered_set<int32_t> active_jobs;
enum FeatureConfigData {
VISION,
@ -138,20 +137,22 @@ df::coord simulate_area_fall(const df::coord &pos) {
// executes dig designations for the specified tile coordinates
inline bool dig_now(color_ostream &out, const df::coord &map_pos) {
auto L = Lua::Core::State;
Lua::StackUnwinder top(L);
if (!lua_checkstack(L, 2) ||
!Lua::PushModulePublic(out, L, "plugins.dig-now", "dig_now_tile"))
return false;
Lua::Push(L, map_pos);
if (!Lua::SafeCall(out, L, 1, 1))
return false;
return lua_toboolean(L, -1);
bool ret = false;
lua_State* state = Lua::Core::State;
static const char* module_name = "plugins.dig-now";
static const char* fn_name = "dig_now_tile";
// the stack layout isn't likely to change, ever
static auto args_lambda = [&map_pos](lua_State* L) {
Lua::Push(L, map_pos);
};
static auto res_lambda = [&ret](lua_State* L) {
ret = lua_toboolean(L, -1);
};
Lua::StackUnwinder top(state);
Lua::CallLuaModuleFunction(out, state, module_name, fn_name, 1, 1, args_lambda, res_lambda);
return ret;
}
// fully heals the unit specified, resurrecting if need be
@ -583,11 +584,14 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_ev
command_result channel_safely(color_ostream &out, std::vector<std::string> &parameters) {
if (!parameters.empty()) {
if (parameters[0] == "runonce") {
CSP::UnpauseEvent();
return DFHack::CR_OK;
} else if (parameters[0] == "rebuild") {
ChannelManager::Get().destroy_groups();
ChannelManager::Get().build_groups();
}
if (parameters.size() >= 2 && parameters.size() <= 3) {
if (parameters[0] == "runonce") {
CSP::UnpauseEvent();
return DFHack::CR_OK;
}
bool state = false;
bool set = false;
if (parameters[0] == "enable") {
@ -600,54 +604,7 @@ command_result channel_safely(color_ostream &out, std::vector<std::string> &para
return DFHack::CR_WRONG_USAGE;
}
try {
if (parameters[1] == "debug") {
auto level = std::abs(std::stol(parameters[2]));
config.debug = true;
switch (level) {
case 1:
DBG_NAME(manager).allowed(DFHack::DebugCategory::LDEBUG);
DBG_NAME(monitor).allowed(DFHack::DebugCategory::LINFO);
DBG_NAME(groups).allowed(DFHack::DebugCategory::LINFO);
DBG_NAME(jobs).allowed(DFHack::DebugCategory::LINFO);
break;
case 2:
DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE);
DBG_NAME(monitor).allowed(DFHack::DebugCategory::LINFO);
DBG_NAME(groups).allowed(DFHack::DebugCategory::LDEBUG);
DBG_NAME(jobs).allowed(DFHack::DebugCategory::LDEBUG);
break;
case 3:
DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE);
DBG_NAME(monitor).allowed(DFHack::DebugCategory::LINFO);
DBG_NAME(groups).allowed(DFHack::DebugCategory::LDEBUG);
DBG_NAME(jobs).allowed(DFHack::DebugCategory::LTRACE);
break;
case 4:
DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE);
DBG_NAME(monitor).allowed(DFHack::DebugCategory::LINFO);
DBG_NAME(groups).allowed(DFHack::DebugCategory::LTRACE);
DBG_NAME(jobs).allowed(DFHack::DebugCategory::LTRACE);
break;
case 5:
DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE);
DBG_NAME(monitor).allowed(DFHack::DebugCategory::LDEBUG);
DBG_NAME(groups).allowed(DFHack::DebugCategory::LTRACE);
DBG_NAME(jobs).allowed(DFHack::DebugCategory::LTRACE);
break;
case 6:
DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE);
DBG_NAME(monitor).allowed(DFHack::DebugCategory::LTRACE);
DBG_NAME(groups).allowed(DFHack::DebugCategory::LTRACE);
DBG_NAME(jobs).allowed(DFHack::DebugCategory::LTRACE);
break;
case 0:
default:
DBG_NAME(monitor).allowed(DFHack::DebugCategory::LERROR);
DBG_NAME(manager).allowed(DFHack::DebugCategory::LERROR);
DBG_NAME(groups).allowed(DFHack::DebugCategory::LERROR);
DBG_NAME(jobs).allowed(DFHack::DebugCategory::LERROR);
}
} else if(parameters[1] == "monitor"){
if(parameters[1] == "monitor"){
if (state != config.monitor_active) {
config.monitor_active = state;
// if this is a fresh start

@ -4,14 +4,15 @@
#include <df/map_block.h>
#include <df/coord.h>
#include <modules/EventManager.h> //hash functions (they should probably get moved at this point, the ones that aren't specifically for EM anyway)
#include <vector>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
using namespace DFHack;
using Group = std::set<df::coord>;
using Group = std::unordered_set<df::coord>;
using Groups = std::vector<Group>;
/* Used to build groups of adjacent channel designations/jobs
@ -26,8 +27,8 @@ using Groups = std::vector<Group>;
*/
class ChannelGroups {
private:
using GroupBlocks = std::set<df::map_block*>;
using GroupsMap = std::map<df::coord, int>;
using GroupBlocks = std::unordered_set<df::map_block*>;
using GroupsMap = std::unordered_map<df::coord, int>;
GroupBlocks group_blocks;
GroupsMap groups_map;
Groups groups;

@ -1,7 +1,11 @@
#pragma once
#include <PluginManager.h>
#include <modules/Job.h>
#include <map>
#include <modules/EventManager.h> //hash functions (they should probably get moved at this point, the ones that aren't specifically for EM anyway)
#include <df/world.h>
#include <df/job.h>
#include <unordered_set>
using namespace DFHack;
@ -17,14 +21,23 @@ using namespace DFHack;
class ChannelJobs {
private:
friend class ChannelGroup;
using Jobs = std::set<df::coord>; // job* will exist until it is complete, and likely beyond
Jobs jobs;
using Jobs = std::unordered_set<df::coord>; // job* will exist until it is complete, and likely beyond
Jobs locations;
public:
void load_channel_jobs();
void clear();
int count(const df::coord &map_pos) const { return jobs.count(map_pos); }
Jobs::iterator erase(const df::coord &map_pos);
Jobs::const_iterator find(const df::coord &map_pos) const;
Jobs::const_iterator begin() const;
Jobs::const_iterator end() const;
void clear() {
locations.clear();
}
int count(const df::coord &map_pos) const { return locations.count(map_pos); }
Jobs::iterator erase(const df::coord &map_pos) {
auto iter = locations.find(map_pos);
if (iter != locations.end()) {
return locations.erase(iter);
}
return iter;
}
Jobs::const_iterator find(const df::coord &map_pos) const { return locations.find(map_pos); }
Jobs::const_iterator begin() const { return locations.begin(); }
Jobs::const_iterator end() const { return locations.end(); }
};

@ -33,9 +33,7 @@ public:
bool exists(const df::coord &map_pos) const { return groups.count(map_pos); }
void debug() {
DEBUG(groups).print(" DEBUGGING GROUPS:\n");
if (config.debug) {
groups.debug_groups();
groups.debug_map();
}
groups.debug_groups();
groups.debug_map();
}
};

@ -64,7 +64,7 @@ inline bool is_safe_fall(const df::coord &map_pos) {
return true; //we require vision, and we can't see below.. so we gotta assume it's safe
}
df::tiletype type = *Maps::getTileType(below);
if (!isOpenTerrain(type)) {
if (!DFHack::isOpenTerrain(type)) {
return true;
}
}
@ -80,10 +80,10 @@ inline bool is_safe_to_dig_down(const df::coord &map_pos) {
return true;
}
df::tiletype type = *Maps::getTileType(pos);
if (zi == 0 && isOpenTerrain(type)) {
if (zi == 0 && DFHack::isOpenTerrain(type)) {
// the starting tile is open space, that's obviously not safe
return false;
} else if (!isOpenTerrain(type)) {
} else if (!DFHack::isOpenTerrain(type)) {
// a tile after the first one is not open space
return true;
}
@ -142,7 +142,9 @@ inline void cancel_job(df::job* job) {
x = pos.x % 16;
y = pos.y % 16;
df::tile_designation &designation = job_block->designation[x][y];
switch (job->job_type) {
auto type = job->job_type;
Job::removeJob(job);
switch (type) {
case job_type::Dig:
designation.bits.dig = df::tile_dig_designation::Default;
break;
@ -165,21 +167,13 @@ inline void cancel_job(df::job* job) {
designation.bits.dig = df::tile_dig_designation::No;
break;
}
Job::removeJob(job);
}
}
template<class Ctr1, class Ctr2, class Ctr3>
void set_difference(const Ctr1 &c1, const Ctr2 &c2, Ctr3 &c3) {
for (const auto &a : c1) {
bool matched = false;
for (const auto &b : c2) {
if (a == b) {
matched = true;
break;
}
}
if (!matched) {
if (!c2.count(a)) {
c3.emplace(a);
}
}

@ -9,7 +9,6 @@ namespace DFHack {
}
struct Configuration {
bool debug = false;
bool monitor_active = false;
bool require_vision = true;
bool insta_dig = false;

@ -3,13 +3,14 @@
#include <modules/Maps.h>
#include <df/coord.h>
#include <df/tiletype.h>
#include <modules/EventManager.h> //hash functions (they should probably get moved at this point, the ones that aren't specifically for EM anyway)
#include <map>
#include <unordered_map>
class TileCache {
private:
TileCache() = default;
std::map<df::coord, df::tiletype> locations;
std::unordered_map<df::coord, df::tiletype> locations;
public:
static TileCache& Get() {
static TileCache instance;
@ -25,11 +26,6 @@ public:
}
bool hasChanged(const df::coord &pos, const df::tiletype &type) {
if (locations.count(pos)) {
if (type != locations.find(pos)->second){
return true;
}
}
return false;
return locations.count(pos) && type != locations[pos];
}
};