Merge pull request #2472 from cppcooper/channel-safely

Updates Channel safely
develop
Myk 2023-01-24 17:05:40 -08:00 committed by GitHub
commit 25658592cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 452 additions and 163 deletions

@ -52,18 +52,17 @@ Commands
-------- --------
:runonce: Run the safety procedures once to set the marker mode of designations. :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 :rebuild: Rebuild the designation group data. You should also read Troubleshooting.
the marker mode isn't being set correctly (mostly for debugging).
Features Features
-------- --------
:require-vision: Toggle whether the dwarves need vision of a tile before channeling to it can be deemed unsafe. (default: enabled) :require-vision: Toggle whether the dwarves need vision of a tile before channeling to it can be deemed unsafe. (default: enabled)
:monitor: Toggle whether to monitor the conditions of active digs. (default: disabled) :risk-averse: Toggles whether to use cave-in prevention. Designations are activated in stages
and their priorities along edges are modified. (default: enabled)
:monitoring: Toggle whether to monitor the conditions of active digs. (default: disabled)
:resurrect: Toggle whether to resurrect units involved in cave-ins, and if monitor is enabled :resurrect: Toggle whether to resurrect units involved in cave-ins, and if monitor is enabled
units who die while digging. (default: disabled) units who die while digging. (default: disabled)
:insta-dig: Toggle whether to use insta-digging on unreachable designations.
Runs on the refresh cycles. (default: disabled)
Settings Settings
-------- --------
@ -74,3 +73,13 @@ Settings
:ignore-threshold: Sets the priority threshold below which designations are processed. You can set to 1 or 0 to :ignore-threshold: Sets the priority threshold below which designations are processed. You can set to 1 or 0 to
effectively disable the scanning. (default: 5) effectively disable the scanning. (default: 5)
:fall-threshold: Sets the fall threshold beyond which is considered unsafe. (default: 1) :fall-threshold: Sets the fall threshold beyond which is considered unsafe. (default: 1)
Troubleshooting
---------------
If designations aren't switching correctly, try putting the designations into marker mode.
Then press . (next) or resume. If you're debugging code you'll want these::
debugfilter set Info channel manager
debugfilter set Debug channel plugin
debugfilter set Trace channel group

@ -93,7 +93,7 @@ dfhack_plugin(buildingplan buildingplan.cpp LINK_LIBRARIES lua)
#dfhack_plugin(changeitem changeitem.cpp) #dfhack_plugin(changeitem changeitem.cpp)
dfhack_plugin(changelayer changelayer.cpp) dfhack_plugin(changelayer changelayer.cpp)
dfhack_plugin(changevein changevein.cpp) dfhack_plugin(changevein changevein.cpp)
#add_subdirectory(channel-safely) add_subdirectory(channel-safely)
dfhack_plugin(cleanconst cleanconst.cpp) dfhack_plugin(cleanconst cleanconst.cpp)
dfhack_plugin(cleaners cleaners.cpp) dfhack_plugin(cleaners cleaners.cpp)
dfhack_plugin(cleanowned cleanowned.cpp) dfhack_plugin(cleanowned cleanowned.cpp)

@ -13,12 +13,63 @@ void ChannelJobs::load_channel_jobs() {
while (node) { while (node) {
df::job* job = node->item; df::job* job = node->item;
node = node->next; node = node->next;
if (is_dig_job(job)) { if (is_channel_job(job)) {
locations.emplace(job->pos); locations.emplace(job->pos);
jobs.emplace(job->pos, job);
} }
} }
} }
bool ChannelJobs::has_cavein_conditions(const df::coord &map_pos) {
auto p = map_pos;
auto ttype = *Maps::getTileType(p);
if (!DFHack::isOpenTerrain(ttype)) {
// check shared neighbour for cave-in conditions
df::coord neighbours[4];
get_connected_neighbours(map_pos, neighbours);
int connectedness = 4;
for (auto n: neighbours) {
if (active.count(n) || DFHack::isOpenTerrain(*Maps::getTileType(n))) {
connectedness--;
}
}
if (!connectedness) {
// do what?
p.z--;
ttype = *Maps::getTileType(p);
if (DFHack::isOpenTerrain(ttype) || DFHack::isFloorTerrain(ttype)) {
return true;
}
}
}
return false;
}
bool ChannelJobs::possible_cavein(const df::coord &job_pos) {
for (auto iter : active) {
if (iter == job_pos) continue;
if (calc_distance(job_pos, iter) <= 2) {
// find neighbours
df::coord n1[8];
df::coord n2[8];
get_neighbours(job_pos, n1);
get_neighbours(iter, n2);
// find shared neighbours
for (int i = 0; i < 7; ++i) {
for (int j = i + 1; j < 8; ++j) {
if (n1[i] == n2[j]) {
if (has_cavein_conditions(n1[i])) {
WARN(jobs).print("Channel-Safely::jobs: Cave-in conditions detected at (" COORD ")\n", COORDARGS(n1[i]));
return true;
}
}
}
}
}
}
return false;
}
// 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 // 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) { void ChannelGroups::add(const df::coord &map_pos) {
// if we've already added this, we don't need to do it again // if we've already added this, we don't need to do it again
@ -82,7 +133,7 @@ void ChannelGroups::add(const df::coord &map_pos) {
TRACE(groups).print(" -> brand new group\n"); TRACE(groups).print(" -> brand new group\n");
// we create a brand-new group to use // we create a brand-new group to use
group_index = groups.size(); group_index = groups.size();
groups.push_back(Group()); groups.emplace_back();
group = &groups[group_index]; group = &groups[group_index];
} }
} }
@ -137,6 +188,7 @@ void ChannelGroups::scan(bool full_scan) {
std::set<df::coord> gone_jobs; std::set<df::coord> gone_jobs;
set_difference(last_jobs, jobs, gone_jobs); set_difference(last_jobs, jobs, gone_jobs);
set_difference(jobs, last_jobs, new_jobs); set_difference(jobs, last_jobs, new_jobs);
INFO(groups).print("gone jobs: %" PRIu64 "\nnew jobs: %" PRIu64 "\n",gone_jobs.size(), new_jobs.size());
for (auto &pos : new_jobs) { for (auto &pos : new_jobs) {
add(pos); add(pos);
} }
@ -155,6 +207,7 @@ void ChannelGroups::scan(bool full_scan) {
if (!full_scan && !block->flags.bits.designated) { if (!full_scan && !block->flags.bits.designated) {
continue; continue;
} }
df::map_block* block_above = Maps::getBlock(bx, by, z+1);
// foreach tile // foreach tile
bool empty_group = true; bool empty_group = true;
for (int16_t lx = 0; lx < 16; ++lx) { for (int16_t lx = 0; lx < 16; ++lx) {
@ -168,7 +221,14 @@ void ChannelGroups::scan(bool full_scan) {
jobs.erase(map_pos); jobs.erase(map_pos);
} }
block->designation[lx][ly].bits.dig = df::tile_dig_designation::No; block->designation[lx][ly].bits.dig = df::tile_dig_designation::No;
} else if (is_dig_designation(block->designation[lx][ly]) || block->occupancy[lx][ly].bits.dig_marked) { } else if (is_dig_designation(block->designation[lx][ly]) || block->occupancy[lx][ly].bits.dig_marked ) {
// We have a dig designated, or marked. Some of these will not need intervention.
if (block_above &&
!is_channel_designation(block->designation[lx][ly]) &&
!is_channel_designation(block_above->designation[lx][ly])) {
// if this tile isn't a channel designation, and doesn't have a channel designation above it.. we can skip it
continue;
}
for (df::block_square_event* event: block->block_events) { for (df::block_square_event* event: block->block_events) {
if (auto evT = virtual_cast<df::block_square_event_designation_priorityst>(event)) { if (auto evT = virtual_cast<df::block_square_event_designation_priorityst>(event)) {
// we want to let the user keep some designations free of being managed // we want to let the user keep some designations free of being managed
@ -203,6 +263,7 @@ void ChannelGroups::scan(bool full_scan) {
void ChannelGroups::clear() { void ChannelGroups::clear() {
debug_map(); debug_map();
WARN(groups).print(" <- clearing groups\n"); WARN(groups).print(" <- clearing groups\n");
jobs.clear();
group_blocks.clear(); group_blocks.clear();
free_spots.clear(); free_spots.clear();
groups_map.clear(); groups_map.clear();
@ -269,13 +330,13 @@ size_t ChannelGroups::count(const df::coord &map_pos) const {
// prints debug info about the groups stored, and their members // prints debug info about the groups stored, and their members
void ChannelGroups::debug_groups() { void ChannelGroups::debug_groups() {
if (DFHack::debug_groups.isEnabled(DebugCategory::LTRACE)) { if (DFHack::debug_groups.isEnabled(DebugCategory::LDEBUG)) {
int idx = 0; int idx = 0;
TRACE(groups).print(" debugging group data\n"); DEBUG(groups).print(" debugging group data\n");
for (auto &group: groups) { for (auto &group: groups) {
TRACE(groups).print(" group %d (size: %zu)\n", idx, group.size()); DEBUG(groups).print(" group %d (size: %zu)\n", idx, group.size());
for (auto &pos: group) { for (auto &pos: group) {
TRACE(groups).print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z); DEBUG(groups).print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z);
} }
idx++; idx++;
} }
@ -284,10 +345,10 @@ void ChannelGroups::debug_groups() {
// prints debug info group mappings // prints debug info group mappings
void ChannelGroups::debug_map() { void ChannelGroups::debug_map() {
if (DFHack::debug_groups.isEnabled(DebugCategory::LDEBUG)) { if (DFHack::debug_groups.isEnabled(DebugCategory::LTRACE)) {
INFO(groups).print("Group Mappings: %zu\n", groups_map.size()); INFO(groups).print("Group Mappings: %zu\n", groups_map.size());
for (auto &pair: groups_map) { for (auto &pair: groups_map) {
DEBUG(groups).print(" map[" COORD "] = %d\n", COORDARGS(pair.first), pair.second); TRACE(groups).print(" map[" COORD "] = %d\n", COORDARGS(pair.first), pair.second);
} }
} }
} }

@ -5,6 +5,25 @@
#include <modules/EventManager.h> //hash function for df::coord #include <modules/EventManager.h> //hash function for df::coord
#include <df/block_square_event_designation_priorityst.h> #include <df/block_square_event_designation_priorityst.h>
df::unit* find_dwarf(const df::coord &map_pos) {
df::unit* nearest = nullptr;
uint32_t distance;
for (auto unit : df::global::world->units.active) {
if (!nearest) {
nearest = unit;
distance = calc_distance(unit->pos, map_pos);
} else if (unit->status.labors[df::unit_labor::MINE]) {
uint32_t d = calc_distance(unit->pos, map_pos);
if (d < distance) {
nearest = unit;
distance = d;
} else if (Maps::canWalkBetween(unit->pos, map_pos)) {
return unit;
}
}
}
return nearest;
}
// sets mark flags as necessary, for all designations // sets mark flags as necessary, for all designations
void ChannelManager::manage_groups() { void ChannelManager::manage_groups() {
@ -30,24 +49,110 @@ void ChannelManager::manage_group(const df::coord &map_pos, bool set_marker_mode
INFO(manager).print("manage_group() is done\n"); INFO(manager).print("manage_group() is done\n");
} }
void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool marker_mode) { void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool marker_mode) const {
INFO(manager).print("manage_group()\n"); INFO(manager).print("manage_group()\n");
if (!set_marker_mode) { if (!set_marker_mode) {
if (has_any_groups_above(groups, group)) { marker_mode = has_any_groups_above(groups, group);
marker_mode = true; }
} else { // cavein prevention
marker_mode = false; bool cavein_possible = false;
uint8_t least_access = 100;
std::unordered_map<df::coord, uint8_t> cavein_candidates;
if (!marker_mode) {
/* To prevent cave-ins we're looking at accessibility of tiles with open space below them
* If it has space below, it has somewhere to fall
* Accessibility tells us how close to a cave-in a tile is, low values are at risk of cave-ins
* To count access, we find a random miner dwarf and count how many tile neighbours they can path to
* */
// find a dwarf to path from
df::coord miner_pos = find_dwarf(*group.begin())->pos;
// Analyze designations
for (const auto &pos: group) {
df::coord below(pos);
below.z--;
const auto below_ttype = *Maps::getTileType(below);
// we can skip designations already queued for insta-digging
if (CSP::dignow_queue.count(pos)) continue;
if (DFHack::isOpenTerrain(below_ttype) || DFHack::isFloorTerrain(below_ttype)) {
// the tile below is not solid earth
// we're counting accessibility then dealing with 0 access
DEBUG(manager).print("analysis: cave-in condition found\n");
INFO(manager).print("(%d) checking accessibility of (" COORD ") from (" COORD ")\n", cavein_possible,COORDARGS(pos),COORDARGS(miner_pos));
auto access = count_accessibility(miner_pos, pos);
if (access == 0) {
TRACE(groups).print(" has 0 access\n");
if (config.insta_dig) {
manage_one(pos, true, false);
dig_now(DFHack::Core::getInstance().getConsole(), pos);
} else {
// todo: engage dig management, swap channel designations for dig
}
} else {
WARN(manager).print(" has %d access\n", access);
cavein_possible = config.riskaverse;
cavein_candidates.emplace(pos, access);
least_access = min(access, least_access);
}
} else if (config.insta_dig && isEntombed(miner_pos, pos)) {
manage_one(pos, true, false);
dig_now(DFHack::Core::getInstance().getConsole(), pos);
}
} }
DEBUG(manager).print("cavein possible(%d)\n"
"%zu candidates\n"
"least access %d\n", cavein_possible, cavein_candidates.size(), least_access);
}
// managing designations
if (!group.empty()) {
DEBUG(manager).print("managing group #%d\n", groups.debugGIndex(*group.begin()));
} }
for (auto &designation: group) { for (auto &pos: group) {
manage_one(group, designation, true, marker_mode); // if no cave-in is possible [or we don't check for], we'll just execute normally and move on
if (!cavein_possible) {
TRACE(manager).print("cave-in evaluated false\n");
assert(manage_one(pos, true, marker_mode));
continue;
}
// cavein is only possible if marker_mode is false
// we want to dig the cavein candidates first, the least accessible ones specifically
const static uint8_t MAX = 84; //arbitrary number that indicates the value has changed
const static uint8_t OFFSET = 2; //value has been tweaked to avoid cave-ins whilst activating as many designations as possible
if (CSP::dignow_queue.count(pos) || (cavein_candidates.count(pos) &&
least_access < MAX && cavein_candidates[pos] <= least_access+OFFSET)) {
TRACE(manager).print("cave-in evaluated true and either of dignow or (%d <= %d)\n", cavein_candidates[pos], least_access+OFFSET);
df::coord local(pos);
local.x %= 16;
local.y %= 16;
auto block = Maps::ensureTileBlock(pos);
// if we don't find the priority in block_events, it probably means bad things
for (df::block_square_event* event: block->block_events) {
if (auto evT = virtual_cast<df::block_square_event_designation_priorityst>(event)) {
// we want to let the user keep some designations free of being managed
auto b = max(0, cavein_candidates[pos] - least_access);
auto v = 1000 + (b * 1700);
DEBUG(manager).print("(" COORD ") 1000+1000(%d) -> %d {least-access: %d}\n",COORDARGS(pos), b, v, least_access);
evT->priority[Coord(local)] = v;
}
}
assert(manage_one(pos, true, false));
continue;
}
// cavein possible, but we failed to meet the criteria for activation
if (cavein_candidates.count(pos)) {
DEBUG(manager).print("cave-in evaluated true and no dignow and (%d > %d)\n", cavein_candidates[pos], least_access+OFFSET);
} else {
DEBUG(manager).print("cave-in evaluated true and no dignow and pos is not a candidate\n");
}
assert(manage_one(pos, true, true));
} }
INFO(manager).print("manage_group() is done\n"); INFO(manager).print("manage_group() is done\n");
} }
bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bool set_marker_mode, bool marker_mode) { bool ChannelManager::manage_one(const df::coord &map_pos, bool set_marker_mode, bool marker_mode) const {
if (Maps::isValidTilePos(map_pos)) { if (Maps::isValidTilePos(map_pos)) {
INFO(manager).print("manage_one(" COORD ")\n", COORDARGS(map_pos)); TRACE(manager).print("manage_one((" COORD "), %d, %d)\n", COORDARGS(map_pos), set_marker_mode, marker_mode);
df::map_block* block = Maps::getTileBlock(map_pos); df::map_block* block = Maps::getTileBlock(map_pos);
// we calculate the position inside the block* // we calculate the position inside the block*
df::coord local(map_pos); df::coord local(map_pos);
@ -57,42 +162,28 @@ bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bo
// ensure that we aren't on the top-most layers // ensure that we aren't on the top-most layers
if (map_pos.z < mapz - 3) { if (map_pos.z < mapz - 3) {
// do we already know whether to set marker mode? // do we already know whether to set marker mode?
if (set_marker_mode) { if (!marker_mode) {
DEBUG(manager).print(" -> marker_mode\n"); // marker_mode is set to true if it is unsafe to dig
// if enabling marker mode, just do it marker_mode = (!set_marker_mode &&
if (marker_mode) { (has_group_above(groups, map_pos) || !is_safe_to_dig_down(map_pos))) ||
tile_occupancy.bits.dig_marked = marker_mode; (set_marker_mode &&
return true; is_channel_designation(block->designation[Coord(local)]) && !is_safe_to_dig_down(map_pos));
} }
// if activating designation, check if it is safe to dig or not a channel designation if (marker_mode) {
if (!is_channel_designation(block->designation[Coord(local)]) || is_safe_to_dig_down(map_pos)) { if (jobs.count(map_pos)) {
if (!block->flags.bits.designated) { cancel_job(map_pos);
block->flags.bits.designated = true;
}
tile_occupancy.bits.dig_marked = false;
TileCache::Get().cache(map_pos, block->tiletype[Coord(local)]);
}
return false;
} else {
// next search for the designation priority
DEBUG(manager).print(" if(has_groups_above())\n");
// check that the group has no incomplete groups directly above it
if (has_group_above(groups, map_pos) || !is_safe_to_dig_down(map_pos)) {
DEBUG(manager).print(" has_groups_above: setting marker mode\n");
tile_occupancy.bits.dig_marked = true;
if (jobs.count(map_pos)) {
jobs.erase(map_pos);
}
WARN(manager).print(" <- manage_one() exits normally\n");
return true;
} }
} else if (!block->flags.bits.designated) {
block->flags.bits.designated = true;
} }
tile_occupancy.bits.dig_marked = marker_mode;
TRACE(manager).print("marker mode %s\n", marker_mode ? "ENABLED" : "DISABLED");
} else { } else {
// if we are though, it should be totally safe to dig // if we are though, it should be totally safe to dig
tile_occupancy.bits.dig_marked = false; tile_occupancy.bits.dig_marked = false;
} }
WARN(manager).print(" <- manage_one() exits normally\n"); TRACE(manager).print(" <- manage_one() exits normally\n");
return true;
} }
return false; return false;
} }

@ -1,7 +1,10 @@
/* Prevent channeling down into known open space. /* Prevent channeling down into known open space.
Author: Josh Cooper Author: Josh Cooper
Created: Aug. 4 2020 Created: Aug. 4 2020
Updated: Nov. 28 2022 Updated: Dec. 8 2022
*/
/*
This skeletal logic has not been kept up-to-date since ~v0.5
Enable plugin: Enable plugin:
-> build groups -> build groups
@ -56,8 +59,6 @@ Updated: Nov. 28 2022
#include <tile-cache.h> #include <tile-cache.h>
#include <Debug.h> #include <Debug.h>
#include <LuaTools.h>
#include <LuaWrapper.h>
#include <PluginManager.h> #include <PluginManager.h>
#include <modules/EventManager.h> #include <modules/EventManager.h>
#include <modules/Units.h> #include <modules/Units.h>
@ -98,7 +99,8 @@ enum FeatureConfigData {
VISION, VISION,
MONITOR, MONITOR,
RESURRECT, RESURRECT,
INSTADIG INSTADIG,
RISKAVERSE
}; };
enum SettingConfigData { enum SettingConfigData {
@ -136,38 +138,13 @@ df::coord simulate_area_fall(const df::coord &pos) {
return lowest; return lowest;
} }
// executes dig designations for the specified tile coordinates
inline bool dig_now(color_ostream &out, const df::coord &map_pos) {
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
inline void resurrect(color_ostream &out, const int32_t &unit) {
std::vector<std::string> params{"-r", "--unit", std::to_string(unit)};
Core::getInstance().runCommand(out,"full-heal", params);
}
namespace CSP { namespace CSP {
std::unordered_map<df::unit*, int32_t> endangered_units; std::unordered_map<df::unit*, int32_t> endangered_units;
std::unordered_map<df::job*, int32_t> job_id_map; std::unordered_map<df::job*, int32_t> job_id_map;
std::unordered_map<int32_t, df::job*> active_jobs; std::unordered_map<int32_t, df::job*> active_jobs;
std::unordered_map<int32_t, df::unit*> active_workers; std::unordered_map<int32_t, df::unit*> active_workers;
std::unordered_map<int32_t, df::coord> last_safe; std::unordered_map<int32_t, df::coord> last_safe;
std::unordered_set<df::coord> dignow_queue; std::unordered_set<df::coord> dignow_queue;
@ -184,10 +161,11 @@ namespace CSP {
void SaveSettings() { void SaveSettings() {
if (pfeature.isValid() && psetting.isValid()) { if (pfeature.isValid() && psetting.isValid()) {
try { try {
pfeature.ival(MONITOR) = config.monitor_active; pfeature.ival(MONITOR) = config.monitoring;
pfeature.ival(VISION) = config.require_vision; pfeature.ival(VISION) = config.require_vision;
pfeature.ival(INSTADIG) = config.insta_dig; pfeature.ival(INSTADIG) = false; //config.insta_dig;
pfeature.ival(RESURRECT) = config.resurrect; pfeature.ival(RESURRECT) = config.resurrect;
pfeature.ival(RISKAVERSE) = config.riskaverse;
psetting.ival(REFRESH_RATE) = config.refresh_freq; psetting.ival(REFRESH_RATE) = config.refresh_freq;
psetting.ival(MONITOR_RATE) = config.monitor_freq; psetting.ival(MONITOR_RATE) = config.monitor_freq;
@ -209,10 +187,11 @@ namespace CSP {
SaveSettings(); SaveSettings();
} else { } else {
try { try {
config.monitor_active = pfeature.ival(MONITOR); config.monitoring = pfeature.ival(MONITOR);
config.require_vision = pfeature.ival(VISION); config.require_vision = pfeature.ival(VISION);
config.insta_dig = pfeature.ival(INSTADIG); config.insta_dig = false; //pfeature.ival(INSTADIG);
config.resurrect = pfeature.ival(RESURRECT); config.resurrect = pfeature.ival(RESURRECT);
config.riskaverse = pfeature.ival(RISKAVERSE);
config.ignore_threshold = psetting.ival(IGNORE_THRESH); config.ignore_threshold = psetting.ival(IGNORE_THRESH);
config.fall_threshold = psetting.ival(FALL_THRESH); config.fall_threshold = psetting.ival(FALL_THRESH);
@ -227,28 +206,36 @@ namespace CSP {
void UnpauseEvent(bool full_scan = false){ void UnpauseEvent(bool full_scan = false){
CoreSuspender suspend; // we need exclusive access to df memory and this call stack doesn't already have a lock CoreSuspender suspend; // we need exclusive access to df memory and this call stack doesn't already have a lock
INFO(monitor).print("UnpauseEvent()\n"); DEBUG(plugin).print("UnpauseEvent()\n");
ChannelManager::Get().build_groups(full_scan); ChannelManager::Get().build_groups(full_scan);
ChannelManager::Get().manage_groups(); ChannelManager::Get().manage_groups();
ChannelManager::Get().debug(); DEBUG(plugin).print("UnpauseEvent() exits\n");
INFO(monitor).print("UnpauseEvent() exits\n");
} }
void JobStartedEvent(color_ostream &out, void* j) { void JobStartedEvent(color_ostream &out, void* j) {
if (enabled && World::isFortressMode() && Maps::IsValid()) { if (enabled && World::isFortressMode() && Maps::IsValid()) {
INFO(jobs).print("JobStartedEvent()\n"); TRACE(jobs).print("JobStartedEvent()\n");
auto job = (df::job*) j; auto job = (df::job*) j;
// validate job type // validate job type
if (ChannelManager::Get().exists(job->pos)) { if (ChannelManager::Get().exists(job->pos)) {
WARN(jobs).print(" valid channel job:\n"); DEBUG(jobs).print(" valid channel job:\n");
df::unit* worker = Job::getWorker(job); df::unit* worker = Job::getWorker(job);
// there is a valid worker (living citizen) on the job? right.. // there is a valid worker (living citizen) on the job? right..
if (worker && Units::isAlive(worker) && Units::isCitizen(worker)) { if (worker && Units::isAlive(worker) && Units::isCitizen(worker)) {
ChannelManager::Get().jobs.mark_active(job->pos);
if (config.riskaverse) {
if (ChannelManager::Get().jobs.possible_cavein(job->pos)) {
cancel_job(job);
ChannelManager::Get().manage_one(job->pos, true, true);
} else {
ChannelManager::Get().manage_group(job->pos, true, false);
}
}
DEBUG(jobs).print(" valid worker:\n"); DEBUG(jobs).print(" valid worker:\n");
// track workers on jobs // track workers on jobs
df::coord &pos = job->pos; df::coord &pos = job->pos;
WARN(jobs).print(" -> Starting job at (" COORD ")\n", COORDARGS(pos)); TRACE(jobs).print(" -> Starting job at (" COORD ")\n", COORDARGS(pos));
if (config.monitor_active || config.resurrect) { if (config.monitoring || config.resurrect) {
job_id_map.emplace(job, job->id); job_id_map.emplace(job, job->id);
active_jobs.emplace(job->id, job); active_jobs.emplace(job->id, job);
active_workers[job->id] = worker; active_workers[job->id] = worker;
@ -263,16 +250,17 @@ namespace CSP {
Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Restricted; Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Restricted;
} }
} }
INFO(jobs).print(" <- JobStartedEvent() exits normally\n"); TRACE(jobs).print(" <- JobStartedEvent() exits normally\n");
} }
} }
void JobCompletedEvent(color_ostream &out, void* j) { void JobCompletedEvent(color_ostream &out, void* j) {
if (enabled && World::isFortressMode() && Maps::IsValid()) { if (enabled && World::isFortressMode() && Maps::IsValid()) {
INFO(jobs).print("JobCompletedEvent()\n"); TRACE(jobs).print("JobCompletedEvent()\n");
auto job = (df::job*) j; auto job = (df::job*) j;
// we only care if the job is a channeling one // we only care if the job is a channeling one
if (ChannelManager::Get().exists(job->pos)) { if (ChannelManager::Get().exists(job->pos)) {
ChannelManager::Get().manage_group(job->pos, true, false);
// check job outcome // check job outcome
auto block = Maps::getTileBlock(job->pos); auto block = Maps::getTileBlock(job->pos);
df::coord local(job->pos); df::coord local(job->pos);
@ -283,15 +271,14 @@ namespace CSP {
// the job can be considered done // the job can be considered done
df::coord below(job->pos); df::coord below(job->pos);
below.z--; below.z--;
WARN(jobs).print(" -> (" COORD ") is marked done, managing group below.\n", COORDARGS(job->pos)); DEBUG(jobs).print(" -> (" COORD ") is marked done, managing group below.\n", COORDARGS(job->pos));
// mark done and manage below // mark done and manage below (and the rest of the group, if there were cavein candidates)
block->designation[Coord(local)].bits.traffic = df::tile_traffic::Normal; block->designation[Coord(local)].bits.traffic = df::tile_traffic::Normal;
ChannelManager::Get().mark_done(job->pos); ChannelManager::Get().mark_done(job->pos);
ChannelManager::Get().manage_group(below); ChannelManager::Get().manage_group(below);
ChannelManager::Get().debug();
if (config.resurrect) { if (config.resurrect) {
// this is the only place we can be 100% sure of "safety" // this is the only place we can be 100% sure of "safety"
// (excluding deadly enemies that will have arrived) // (excluding deadly enemies that may have arrived, and future digging)
if (active_workers.count(job->id)) { if (active_workers.count(job->id)) {
df::unit* worker = active_workers[job->id]; df::unit* worker = active_workers[job->id];
last_safe[worker->id] = worker->pos; last_safe[worker->id] = worker->pos;
@ -304,7 +291,7 @@ namespace CSP {
active_workers.erase(job->id); active_workers.erase(job->id);
active_jobs.erase(job->id); active_jobs.erase(job->id);
} }
INFO(jobs).print("JobCompletedEvent() exits\n"); TRACE(jobs).print("JobCompletedEvent() exits\n");
} }
} }
@ -319,9 +306,9 @@ namespace CSP {
switch (report->type) { switch (report->type) {
case announcement_type::CANCEL_JOB: case announcement_type::CANCEL_JOB:
if (config.insta_dig) { if (config.insta_dig) {
if (report->text.find("cancels Dig") != std::string::npos) { if (report->text.find("cancels Dig") != std::string::npos ||
dignow_queue.emplace(report->pos); report->text.find("path") != std::string::npos) {
} else if (report->text.find("path") != std::string::npos) {
dignow_queue.emplace(report->pos); dignow_queue.emplace(report->pos);
} }
DEBUG(plugin).print("%d, pos: " COORD ", pos2: " COORD "\n%s\n", report_id, COORDARGS(report->pos), DEBUG(plugin).print("%d, pos: " COORD ", pos2: " COORD "\n%s\n", report_id, COORDARGS(report->pos),
@ -365,9 +352,7 @@ namespace CSP {
} }
void OnUpdate(color_ostream &out) { void OnUpdate(color_ostream &out) {
static auto print_res_msg = [](df::unit* unit) { CoreSuspender suspend;
WARN(plugin).print("Channel-Safely: Resurrecting..\n [id: %d]\n", unit->id);
};
if (enabled && World::isFortressMode() && Maps::IsValid() && !World::ReadPauseState()) { if (enabled && World::isFortressMode() && Maps::IsValid() && !World::ReadPauseState()) {
static int32_t last_tick = df::global::world->frame_counter; static int32_t last_tick = df::global::world->frame_counter;
static int32_t last_monitor_tick = df::global::world->frame_counter; static int32_t last_monitor_tick = df::global::world->frame_counter;
@ -382,17 +367,18 @@ namespace CSP {
if (config.insta_dig) { if (config.insta_dig) {
TRACE(monitor).print(" -> evaluate dignow queue\n"); TRACE(monitor).print(" -> evaluate dignow queue\n");
for (auto iter = dignow_queue.begin(); iter != dignow_queue.end();) { for (auto iter = dignow_queue.begin(); iter != dignow_queue.end();) {
dig_now(out, *iter); // teleports units to the bottom of a simulated fall auto map_pos = *iter;
dig_now(out, map_pos); // teleports units to the bottom of a simulated fall
ChannelManager::Get().mark_done(map_pos);
iter = dignow_queue.erase(iter); iter = dignow_queue.erase(iter);
DEBUG(plugin).print(">INSTA-DIGGING<\n");
} }
} }
UnpauseEvent(); UnpauseEvent(false);
TRACE(monitor).print("OnUpdate() refresh done\n"); TRACE(monitor).print("OnUpdate() refresh done\n");
} }
// Clean up stale df::job* // Clean up stale df::job*
if ((config.monitor_active || config.resurrect) && tick - last_tick >= 1) { if ((config.monitoring || config.resurrect) && tick - last_tick >= 1) {
last_tick = tick; last_tick = tick;
// make note of valid jobs // make note of valid jobs
std::unordered_map<int32_t, df::job*> valid_jobs; std::unordered_map<int32_t, df::job*> valid_jobs;
@ -415,7 +401,7 @@ namespace CSP {
} }
// Monitoring Active and Resurrecting Dead // Monitoring Active and Resurrecting Dead
if (config.monitor_active && tick - last_monitor_tick >= config.monitor_freq) { if (config.monitoring && tick - last_monitor_tick >= config.monitor_freq) {
last_monitor_tick = tick; last_monitor_tick = tick;
TRACE(monitor).print("OnUpdate() monitoring now\n"); TRACE(monitor).print("OnUpdate() monitoring now\n");
@ -427,15 +413,9 @@ namespace CSP {
if (!Maps::isValidTilePos(job->pos)) continue; if (!Maps::isValidTilePos(job->pos)) continue;
TRACE(monitor).print(" -> check for job in tracking\n"); TRACE(monitor).print(" -> check for job in tracking\n");
if (Units::isAlive(unit)) { if (Units::isAlive(unit)) {
if (!config.monitor_active) continue; if (!config.monitoring) continue;
TRACE(monitor).print(" -> compare positions of worker and job\n"); TRACE(monitor).print(" -> compare positions of worker and job\n");
// save position
if (unit->pos != job->pos && isFloorTerrain(*Maps::getTileType(unit->pos))) {
// worker is probably safe right now
continue;
}
// check for fall safety // check for fall safety
if (unit->pos == job->pos && !is_safe_fall(job->pos)) { if (unit->pos == job->pos && !is_safe_fall(job->pos)) {
// unsafe // unsafe
@ -456,7 +436,7 @@ namespace CSP {
Maps::getTileOccupancy(job->pos)->bits.dig_marked = true; Maps::getTileOccupancy(job->pos)->bits.dig_marked = true;
// prevent algorithm from re-enabling designation // prevent algorithm from re-enabling designation
for (auto &be: Maps::getBlock(job->pos)->block_events) { ; for (auto &be: Maps::getBlock(job->pos)->block_events) {
if (auto bsedp = virtual_cast<df::block_square_event_designation_priorityst>( if (auto bsedp = virtual_cast<df::block_square_event_designation_priorityst>(
be)) { be)) {
df::coord local(job->pos); df::coord local(job->pos);
@ -475,7 +455,6 @@ namespace CSP {
df::coord lowest = simulate_fall(last_safe[unit->id]); df::coord lowest = simulate_fall(last_safe[unit->id]);
Units::teleport(unit, lowest); Units::teleport(unit, lowest);
} }
print_res_msg(unit);
} }
} }
TRACE(monitor).print("OnUpdate() monitoring done\n"); TRACE(monitor).print("OnUpdate() monitoring done\n");
@ -504,7 +483,6 @@ namespace CSP {
df::coord lowest = simulate_fall(last_safe[unit->id]); df::coord lowest = simulate_fall(last_safe[unit->id]);
Units::teleport(unit, lowest); Units::teleport(unit, lowest);
} }
print_res_msg(unit);
} }
} }
} }
@ -547,7 +525,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
EM::registerListener(EventType::JOB_COMPLETED, jobCompletionHandler, plugin_self); EM::registerListener(EventType::JOB_COMPLETED, jobCompletionHandler, plugin_self);
// manage designations to start off (first time building groups [very important]) // manage designations to start off (first time building groups [very important])
out.print("channel-safely: enabled!\n"); out.print("channel-safely: enabled!\n");
CSP::UnpauseEvent(); CSP::UnpauseEvent(true);
} else if (!enable) { } else if (!enable) {
// don't need the groups if the plugin isn't going to be enabled // don't need the groups if the plugin isn't going to be enabled
EM::unregisterAll(plugin_self); EM::unregisterAll(plugin_self);
@ -562,11 +540,10 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
case SC_UNPAUSED: case SC_UNPAUSED:
if (enabled && World::isFortressMode() && Maps::IsValid()) { if (enabled && World::isFortressMode() && Maps::IsValid()) {
// manage all designations on unpause // manage all designations on unpause
CSP::UnpauseEvent(); CSP::UnpauseEvent(true);
} }
break; break;
case SC_MAP_LOADED: case SC_MAP_LOADED:
case SC_WORLD_LOADED:
// cache the map size // cache the map size
Maps::getSize(mapx, mapy, mapz); Maps::getSize(mapx, mapy, mapz);
CSP::ClearData(); CSP::ClearData();
@ -609,24 +586,27 @@ command_result channel_safely(color_ostream &out, std::vector<std::string> &para
return DFHack::CR_WRONG_USAGE; return DFHack::CR_WRONG_USAGE;
} }
try { try {
if(parameters[1] == "monitor"){ if(parameters[1] == "monitoring"){
if (state != config.monitor_active) { if (state != config.monitoring) {
config.monitor_active = state; config.monitoring = state;
// if this is a fresh start // if this is a fresh start
if (state && !config.resurrect) { if (state && !config.resurrect) {
// we need a fresh start // we need a fresh start
CSP::active_workers.clear(); CSP::active_workers.clear();
} }
} }
} else if (parameters[1] == "risk-averse") {
config.riskaverse = state;
} else if (parameters[1] == "require-vision") { } else if (parameters[1] == "require-vision") {
config.require_vision = state; config.require_vision = state;
} else if (parameters[1] == "insta-dig") { } else if (parameters[1] == "insta-dig") {
config.insta_dig = state; //config.insta_dig = state;
config.insta_dig = false;
} else if (parameters[1] == "resurrect") { } else if (parameters[1] == "resurrect") {
if (state != config.resurrect) { if (state != config.resurrect) {
config.resurrect = state; config.resurrect = state;
// if this is a fresh start // if this is a fresh start
if (state && !config.monitor_active) { if (state && !config.monitoring) {
// we need a fresh start // we need a fresh start
CSP::active_workers.clear(); CSP::active_workers.clear();
} }
@ -656,9 +636,10 @@ command_result channel_safely(color_ostream &out, std::vector<std::string> &para
} else { } else {
out.print("Channel-Safely is %s\n", enabled ? "ENABLED." : "DISABLED."); out.print("Channel-Safely is %s\n", enabled ? "ENABLED." : "DISABLED.");
out.print(" FEATURES:\n"); out.print(" FEATURES:\n");
out.print(" %-20s\t%s\n", "monitor-active: ", config.monitor_active ? "on." : "off."); out.print(" %-20s\t%s\n", "risk-averse: ", config.riskaverse ? "on." : "off.");
out.print(" %-20s\t%s\n", "monitoring: ", config.monitoring ? "on." : "off.");
out.print(" %-20s\t%s\n", "require-vision: ", config.require_vision ? "on." : "off."); out.print(" %-20s\t%s\n", "require-vision: ", config.require_vision ? "on." : "off.");
out.print(" %-20s\t%s\n", "insta-dig: ", config.insta_dig ? "on." : "off."); //out.print(" %-20s\t%s\n", "insta-dig: ", config.insta_dig ? "on." : "off.");
out.print(" %-20s\t%s\n", "resurrect: ", config.resurrect ? "on." : "off."); out.print(" %-20s\t%s\n", "resurrect: ", config.resurrect ? "on." : "off.");
out.print(" SETTINGS:\n"); out.print(" SETTINGS:\n");
out.print(" %-20s\t%" PRIi32 "\n", "refresh-freq: ", config.refresh_freq); out.print(" %-20s\t%" PRIi32 "\n", "refresh-freq: ", config.refresh_freq);

@ -12,7 +12,7 @@
using namespace DFHack; using namespace DFHack;
using Group = std::unordered_set<df::coord>; using Group = std::set<df::coord>;
using Groups = std::vector<Group>; using Groups = std::vector<Group>;
/* Used to build groups of adjacent channel designations/jobs /* Used to build groups of adjacent channel designations/jobs
@ -37,6 +37,12 @@ private:
protected: protected:
void add(const df::coord &map_pos); void add(const df::coord &map_pos);
public: public:
int debugGIndex(const df::coord &map_pos) const {
if (groups_map.count(map_pos)) {
return groups_map.find(map_pos)->second;
}
return -1;
}
explicit ChannelGroups(ChannelJobs &jobs) : jobs(jobs) { groups.reserve(200); } explicit ChannelGroups(ChannelJobs &jobs) : jobs(jobs) { groups.reserve(200); }
void scan_one(const df::coord &map_pos); void scan_one(const df::coord &map_pos);
void scan(bool full_scan = false); void scan(bool full_scan = false);

@ -6,6 +6,7 @@
#include <df/job.h> #include <df/job.h>
#include <unordered_set> #include <unordered_set>
#include <unordered_map>
using namespace DFHack; using namespace DFHack;
@ -20,23 +21,32 @@ using namespace DFHack;
*/ */
class ChannelJobs { class ChannelJobs {
private: private:
friend class ChannelGroup;
using Jobs = std::unordered_set<df::coord>; // job* will exist until it is complete, and likely beyond using Jobs = std::unordered_set<df::coord>; // job* will exist until it is complete, and likely beyond
std::unordered_map<df::coord, df::job*> jobs;
Jobs locations; Jobs locations;
Jobs active;
protected:
bool has_cavein_conditions(const df::coord &map_pos);
public: public:
void load_channel_jobs(); void load_channel_jobs();
bool possible_cavein(const df::coord &job_pos);
void mark_active(const df::coord &map_pos) { active.emplace(map_pos); }
void clear() { void clear() {
active.clear();
locations.clear(); locations.clear();
jobs.clear();
} }
int count(const df::coord &map_pos) const { return locations.count(map_pos); } int count(const df::coord &map_pos) const { return locations.count(map_pos); }
Jobs::iterator erase(const df::coord &map_pos) { Jobs::iterator erase(const df::coord &map_pos) {
active.erase(map_pos);
jobs.erase(map_pos);
auto iter = locations.find(map_pos); auto iter = locations.find(map_pos);
if (iter != locations.end()) { if (iter != locations.end()) {
return locations.erase(iter); return locations.erase(iter);
} }
return iter; return iter;
} }
df::job* find_job(const df::coord &map_pos) const { return jobs.count(map_pos) ? jobs.find(map_pos)->second : nullptr; }
Jobs::const_iterator find(const df::coord &map_pos) const { return locations.find(map_pos); } 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 begin() const { return locations.begin(); }
Jobs::const_iterator end() const { return locations.end(); } Jobs::const_iterator end() const { return locations.end(); }

@ -12,11 +12,11 @@ using namespace DFHack;
// Uses GroupData to detect an unsafe work environment // Uses GroupData to detect an unsafe work environment
class ChannelManager { class ChannelManager {
private: private:
ChannelJobs jobs;
ChannelManager()= default; ChannelManager()= default;
protected: protected:
public: public:
ChannelGroups groups = ChannelGroups(jobs); ChannelGroups groups = ChannelGroups(jobs);
ChannelJobs jobs;
static ChannelManager& Get(){ static ChannelManager& Get(){
static ChannelManager instance; static ChannelManager instance;
@ -27,13 +27,13 @@ public:
void destroy_groups() { groups.clear(); debug(); } void destroy_groups() { groups.clear(); debug(); }
void manage_groups(); void manage_groups();
void manage_group(const df::coord &map_pos, bool set_marker_mode = false, bool marker_mode = false); void manage_group(const df::coord &map_pos, bool set_marker_mode = false, bool marker_mode = false);
void manage_group(const Group &group, bool set_marker_mode = false, bool marker_mode = false); void manage_group(const Group &group, bool set_marker_mode = false, bool marker_mode = false) const;
bool manage_one(const Group &group, const df::coord &map_pos, bool set_marker_mode = false, bool marker_mode = false); bool manage_one(const df::coord &map_pos, bool set_marker_mode = false, bool marker_mode = false) const;
void mark_done(const df::coord &map_pos); void mark_done(const df::coord &map_pos);
bool exists(const df::coord &map_pos) const { return groups.count(map_pos); } bool exists(const df::coord &map_pos) const { return groups.count(map_pos); }
void debug() { void debug() {
DEBUG(groups).print(" DEBUGGING GROUPS:\n"); DEBUG(groups).print(" DEBUGGING GROUPS:\n");
groups.debug_groups();
groups.debug_map(); groups.debug_map();
groups.debug_groups();
} }
}; };

@ -2,21 +2,42 @@
#include "plugin.h" #include "plugin.h"
#include "channel-manager.h" #include "channel-manager.h"
#include <TileTypes.h>
#include <LuaTools.h>
#include <LuaWrapper.h>
#include <modules/Maps.h> #include <modules/Maps.h>
#include <df/job.h> #include <df/job.h>
#include <TileTypes.h>
#include <cinttypes> #include <cinttypes>
#include <unordered_set> #include <unordered_set>
#include <random>
#define Coord(id) id.x][id.y #define Coord(id) (id).x][(id).y
#define COORD "%" PRIi16 " %" PRIi16 " %" PRIi16 #define COORD "%" PRIi16 ",%" PRIi16 ",%" PRIi16
#define COORDARGS(id) id.x, id.y, id.z #define COORDARGS(id) (id).x, (id).y, (id).z
namespace CSP { namespace CSP {
extern std::unordered_set<df::coord> dignow_queue; extern std::unordered_set<df::coord> dignow_queue;
} }
inline uint32_t calc_distance(df::coord p1, df::coord p2) {
// calculate chebyshev (chessboard) distance
uint32_t distance = abs(p2.z - p1.z);
distance += max(abs(p2.x - p1.x), abs(p2.y - p1.y));
return distance;
}
inline void get_connected_neighbours(const df::coord &map_pos, df::coord(&neighbours)[4]) {
neighbours[0] = map_pos;
neighbours[1] = map_pos;
neighbours[2] = map_pos;
neighbours[3] = map_pos;
neighbours[0].y--;
neighbours[1].x--;
neighbours[2].x++;
neighbours[3].y++;
}
inline void get_neighbours(const df::coord &map_pos, df::coord(&neighbours)[8]) { inline void get_neighbours(const df::coord &map_pos, df::coord(&neighbours)[8]) {
neighbours[0] = map_pos; neighbours[0] = map_pos;
neighbours[1] = map_pos; neighbours[1] = map_pos;
@ -36,6 +57,36 @@ inline void get_neighbours(const df::coord &map_pos, df::coord(&neighbours)[8])
neighbours[7].x++; neighbours[7].y++; neighbours[7].x++; neighbours[7].y++;
} }
inline uint8_t count_accessibility(const df::coord &unit_pos, const df::coord &map_pos) {
df::coord neighbours[8];
df::coord connections[4];
get_neighbours(map_pos, neighbours);
get_connected_neighbours(map_pos, connections);
uint8_t accessibility = Maps::canWalkBetween(unit_pos, map_pos) ? 1 : 0;
for (auto n: neighbours) {
if (Maps::canWalkBetween(unit_pos, n)) {
accessibility++;
}
}
for (auto n : connections) {
if (Maps::canWalkBetween(unit_pos, n)) {
accessibility++;
}
}
return accessibility;
}
inline bool isEntombed(const df::coord &unit_pos, const df::coord &map_pos) {
if (Maps::canWalkBetween(unit_pos, map_pos)) {
return false;
}
df::coord neighbours[8];
get_neighbours(map_pos, neighbours);
return std::all_of(neighbours+0, neighbours+8, [&unit_pos](df::coord n) {
return !Maps::canWalkBetween(unit_pos, n);
});
}
inline bool is_dig_job(const df::job* job) { inline bool is_dig_job(const df::job* job) {
return job->job_type == df::job_type::Dig || job->job_type == df::job_type::DigChannel; return job->job_type == df::job_type::Dig || job->job_type == df::job_type::DigChannel;
} }
@ -92,22 +143,6 @@ inline bool is_safe_to_dig_down(const df::coord &map_pos) {
return false; return false;
} }
inline bool can_reach_designation(const df::coord &start, const df::coord &end) {
if (start != end) {
if (!Maps::canWalkBetween(start, end)) {
df::coord neighbours[8];
get_neighbours(end, neighbours);
for (auto &pos: neighbours) {
if (Maps::isValidTilePos(pos) && Maps::canWalkBetween(start, pos)) {
return true;
}
}
return false;
}
}
return true;
}
inline bool has_unit(const df::tile_occupancy* occupancy) { inline bool has_unit(const df::tile_occupancy* occupancy) {
return occupancy->bits.unit || occupancy->bits.unit_grounded; return occupancy->bits.unit || occupancy->bits.unit_grounded;
} }
@ -135,15 +170,20 @@ inline bool has_any_groups_above(const ChannelGroups &groups, const Group &group
} }
inline void cancel_job(df::job* job) { inline void cancel_job(df::job* job) {
if (job != nullptr) { if (job) {
df::coord &pos = job->pos; const df::coord &pos = job->pos;
df::map_block* job_block = Maps::getTileBlock(pos); df::map_block* job_block = Maps::getTileBlock(pos);
uint16_t x, y; uint16_t x, y;
x = pos.x % 16; x = pos.x % 16;
y = pos.y % 16; y = pos.y % 16;
df::tile_designation &designation = job_block->designation[x][y]; df::tile_designation &designation = job_block->designation[x][y];
auto type = job->job_type; auto type = job->job_type;
ChannelManager::Get().jobs.erase(pos);
Job::removeWorker(job);
Job::removePostings(job, true);
Job::removeJob(job); Job::removeJob(job);
job_block->flags.bits.designated = true;
job_block->occupancy[x][y].bits.dig_marked = true;
switch (type) { switch (type) {
case job_type::Dig: case job_type::Dig:
designation.bits.dig = df::tile_dig_designation::Default; designation.bits.dig = df::tile_dig_designation::Default;
@ -170,6 +210,96 @@ inline void cancel_job(df::job* job) {
} }
} }
inline void cancel_job(const df::coord &map_pos) {
cancel_job(ChannelManager::Get().jobs.find_job(map_pos));
ChannelManager::Get().jobs.erase(map_pos);
}
// executes dig designations for the specified tile coordinates
inline bool dig_now(color_ostream &out, const df::coord &map_pos) {
static std::default_random_engine rng;
std::uniform_int_distribution<> dist(0,5);
out.color(color_value::COLOR_YELLOW);
out.print("channel-safely: insta-dig: digging (" COORD ")<\n", COORDARGS(map_pos));
df::coord below(map_pos);
below.z--;
auto ttype_below = *Maps::getTileType(below);
if (isOpenTerrain(ttype_below) || isFloorTerrain(ttype_below)) {
*Maps::getTileType(map_pos) = tiletype::OpenSpace;
} else {
auto ttype_p = Maps::getTileType(map_pos);
if (isSoilMaterial(*ttype_p)) {
switch(dist(rng)) {
case 0:
*ttype_p = tiletype::SoilFloor1;
break;
case 1:
*ttype_p = tiletype::SoilFloor2;
break;
case 2:
*ttype_p = tiletype::SoilFloor3;
break;
case 3:
*ttype_p = tiletype::SoilFloor4;
break;
default:
*ttype_p = tiletype::SoilFloor1;
break;
}
} else if (isStoneMaterial(*ttype_p)) {
switch(dist(rng)) {
case 0:
*ttype_p = tiletype::FeatureFloor1;
break;
case 1:
*ttype_p = tiletype::FeatureFloor2;
break;
case 2:
*ttype_p = tiletype::FeatureFloor3;
break;
case 3:
*ttype_p = tiletype::FeatureFloor4;
break;
default:
*ttype_p = tiletype::MineralFloor1;
break;
}
} else {
out.print("Unknown type\n");
return false;
}
}
return true;
/*
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
inline void resurrect(color_ostream &out, const int32_t &unit) {
out.color(DFHack::COLOR_RED);
out.print("channel-safely: resurrecting [id: %d]\n", unit);
std::vector<std::string> params{"-r", "--unit", std::to_string(unit)};
Core::getInstance().runCommand(out,"full-heal", params);
}
template<class Ctr1, class Ctr2, class Ctr3> template<class Ctr1, class Ctr2, class Ctr3>
void set_difference(const Ctr1 &c1, const Ctr2 &c2, Ctr3 &c3) { void set_difference(const Ctr1 &c1, const Ctr2 &c2, Ctr3 &c3) {
for (const auto &a : c1) { for (const auto &a : c1) {

@ -9,7 +9,8 @@ namespace DFHack {
} }
struct Configuration { struct Configuration {
bool monitor_active = false; bool riskaverse = true;
bool monitoring = false;
bool require_vision = true; bool require_vision = true;
bool insta_dig = false; bool insta_dig = false;
bool resurrect = false; bool resurrect = false;