Implements plugin: channel-safely v1.0a

develop
Josh Cooper 2022-11-10 15:59:48 -08:00
parent c4e55f1cc9
commit 6c68e72295
3 changed files with 141 additions and 70 deletions

@ -49,7 +49,8 @@ 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) :monitor: Toggle whether to monitor the conditions of active digs. (default: disabled)
:resurrect: Toggle whether to resurrect dwarves killed on the job. (default: disabled) :resurrect: Toggle whether to resurrect units involved in cave-ins, and if monitor is enabled
units who die while digging. (default: disabled)
:insta-dig: Toggle whether to use insta-digging on unreachable designations. :insta-dig: Toggle whether to use insta-digging on unreachable designations.
Runs on the refresh cycles. (default: disabled) Runs on the refresh cycles. (default: disabled)

@ -2,6 +2,7 @@
#include <tile-cache.h> #include <tile-cache.h>
#include <inlines.h> #include <inlines.h>
#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>
/** /**
@ -104,5 +105,6 @@ bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bo
void ChannelManager::mark_done(const df::coord &map_pos) { void ChannelManager::mark_done(const df::coord &map_pos) {
groups.remove(map_pos); groups.remove(map_pos);
jobs.erase(map_pos); jobs.erase(map_pos);
CSP::dignow_queue.erase(map_pos);
TileCache::Get().uncache(map_pos); TileCache::Get().uncache(map_pos);
} }

@ -109,6 +109,33 @@ enum SettingConfigData {
FALL_THRESH FALL_THRESH
}; };
// dig-now.cpp
df::coord simulate_fall(const df::coord &pos) {
df::coord resting_pos(pos);
while (Maps::ensureTileBlock(resting_pos)) {
df::tiletype tt = *Maps::getTileType(resting_pos);
df::tiletype_shape_basic basic_shape = tileShapeBasic(tileShape(tt));
if (isWalkable(tt) && basic_shape != df::tiletype_shape_basic::Open)
break;
--resting_pos.z;
}
return resting_pos;
}
df::coord simulate_area_fall(const df::coord &pos) {
df::coord neighbours[8]{};
get_neighbours(pos, neighbours);
df::coord lowest = simulate_fall(pos);
for (auto p : neighbours) {
if (p.z < lowest.z) {
lowest = p;
}
}
return lowest;
}
// executes dig designations for the specified tile coordinates // executes dig designations for the specified tile coordinates
inline bool dig_now(color_ostream &out, const df::coord &map_pos) { inline bool dig_now(color_ostream &out, const df::coord &map_pos) {
auto L = Lua::Core::State; auto L = Lua::Core::State;
@ -134,14 +161,24 @@ inline void resurrect(color_ostream &out, const int32_t &unit) {
} }
namespace CSP { namespace CSP {
std::unordered_set<df::unit*> endangered_workers; std::unordered_map<df::unit*, int32_t> endangered_units;
std::unordered_map<df::job*, int32_t> job_ids; 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;
void ClearData() {
ChannelManager::Get().destroy_groups();
dignow_queue.clear();
last_safe.clear();
endangered_units.clear();
active_workers.clear();
active_jobs.clear();
job_id_map.clear();
}
void SaveSettings() { void SaveSettings() {
if (pfeature.isValid() && psetting.isValid()) { if (pfeature.isValid() && psetting.isValid()) {
try { try {
@ -209,9 +246,14 @@ namespace CSP {
df::coord &pos = job->pos; df::coord &pos = job->pos;
WARN(jobs).print(" -> Starting job at (" COORD ")\n", COORDARGS(pos)); WARN(jobs).print(" -> Starting job at (" COORD ")\n", COORDARGS(pos));
if (config.monitor_active || config.resurrect) { if (config.monitor_active || config.resurrect) {
job_ids.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;
if (config.resurrect) {
// this is the only place we can be 100% sure of "safety"
// (excluding deadly enemies that will have arrived)
last_safe[worker->id] = worker->pos;
}
} }
// set tile to restricted // set tile to restricted
TRACE(jobs).print(" setting job tile to restricted\n"); TRACE(jobs).print(" setting job tile to restricted\n");
@ -244,28 +286,27 @@ namespace CSP {
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(); ChannelManager::Get().debug();
} else { if (config.resurrect) {
// the tile is unchanged // this is the only place we can be 100% sure of "safety"
df::unit* worker = active_workers[job->id]; // (excluding deadly enemies that will have arrived)
endangered_workers.emplace(active_workers[job->id]); if (active_workers.count(job->id)) {
ERR(jobs).print("() -> job at (" COORD ") is done, but (" COORD ") doesn't appear done.\n",COORDARGS(worker->pos), COORDARGS(job->pos)); df::unit* worker = active_workers[job->id];
if (config.insta_dig) { last_safe[worker->id] = worker->pos;
dignow_queue.emplace(job->pos); }
} }
} }
// clean up // clean up
if (!config.resurrect) { auto jp = active_jobs[job->id];
auto jp = active_jobs[job->id]; job_id_map.erase(jp);
job_ids.erase(jp); 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"); INFO(jobs).print("JobCompletedEvent() exits\n");
} }
} }
void NewReportEvent(color_ostream &out, void* r) { void NewReportEvent(color_ostream &out, void* r) {
int32_t tick = df::global::world->frame_counter;
auto report_id = (int32_t)(intptr_t(r)); auto report_id = (int32_t)(intptr_t(r));
if (df::global::world) { if (df::global::world) {
std::vector<df::report*> &reports = df::global::world->status.reports; std::vector<df::report*> &reports = df::global::world->status.reports;
@ -274,17 +315,46 @@ namespace CSP {
df::report* report = reports.at(idx); df::report* report = reports.at(idx);
switch (report->type) { switch (report->type) {
case announcement_type::CANCEL_JOB: case announcement_type::CANCEL_JOB:
out.print("%d, pos: " COORD ", pos2: " COORD "\n%s\n", report_id, COORDARGS(report->pos), COORDARGS(report->pos2), report->text.c_str()); if (config.insta_dig) {
if (report->text.find("Dangerous") != std::string::npos) { if (report->text.find("cancels Dig") != std::string::npos) {
dignow_queue.emplace(report->pos); dignow_queue.emplace(report->pos);
break; } else if (report->text.find("path") != std::string::npos) {
} else if (!report->flags.bits.unconscious) { dignow_queue.emplace(report->pos);
break; }
DEBUG(plugin).print("%d, pos: " COORD ", pos2: " COORD "\n%s\n", report_id, COORDARGS(report->pos),
COORDARGS(report->pos2), report->text.c_str());
} }
break;
case announcement_type::CAVE_COLLAPSE: case announcement_type::CAVE_COLLAPSE:
for (auto p : active_workers) { if (config.resurrect) {
endangered_workers.emplace(p.second); DEBUG(plugin).print("CAVE IN\n%d, pos: " COORD ", pos2: " COORD "\n%s\n", report_id, COORDARGS(report->pos),
COORDARGS(report->pos2), report->text.c_str());
df::coord below = report->pos;
below.z -= 1;
below = simulate_area_fall(below);
df::coord areaMin{report->pos};
df::coord areaMax{areaMin};
areaMin.x -= 15;
areaMin.y -= 15;
areaMax.x += 15;
areaMax.y += 15;
areaMin.z = below.z;
areaMax.z += 1;
std::vector<df::unit*> units;
Units::getUnitsInBox(units, COORDARGS(areaMin), COORDARGS(areaMax));
for (auto unit: units) {
endangered_units[unit] = tick;
DEBUG(plugin).print(" [id %d] was near a cave in.\n", unit->id);
}
for (auto unit : world->units.all) {
if (last_safe.count(unit->id)) {
endangered_units[unit] = tick;
DEBUG(plugin).print(" [id %d] is/was a worker, we'll track them too.\n", unit->id);
}
}
} }
break;
default: default:
break; break;
} }
@ -292,6 +362,9 @@ namespace CSP {
} }
void OnUpdate(color_ostream &out) { void OnUpdate(color_ostream &out) {
static auto print_res_msg = [](df::unit* unit) {
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;
@ -303,24 +376,16 @@ namespace CSP {
if (tick - last_refresh_tick >= config.refresh_freq) { if (tick - last_refresh_tick >= config.refresh_freq) {
last_refresh_tick = tick; last_refresh_tick = tick;
TRACE(monitor).print("OnUpdate() refreshing now\n"); TRACE(monitor).print("OnUpdate() refreshing now\n");
UnpauseEvent();
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();) {
if (!has_unit(Maps::getTileOccupancy(*iter))) { dig_now(out, *iter); // teleports units to the bottom of a simulated fall
dig_now(out, *iter); iter = dignow_queue.erase(iter);
iter = dignow_queue.erase(iter); DEBUG(plugin).print(">INSTA-DIGGING<\n");
WARN(plugin).print(">INSTA-DIGGING<\n");
continue;
} else {
// todo: teleport?
//Units::teleport()
}
++iter;
} }
TRACE(monitor).print("OnUpdate() refresh done\n");
} }
UnpauseEvent();
TRACE(monitor).print("OnUpdate() refresh done\n");
} }
// Clean up stale df::job* // Clean up stale df::job*
@ -339,8 +404,8 @@ namespace CSP {
std::unordered_set<df::job*> erase; std::unordered_set<df::job*> erase;
map_value_difference(active_jobs, valid_jobs, erase); map_value_difference(active_jobs, valid_jobs, erase);
for (auto j : erase) { for (auto j : erase) {
auto id = job_ids[j]; auto id = job_id_map[j];
job_ids.erase(j); job_id_map.erase(j);
active_jobs.erase(id); active_jobs.erase(id);
active_workers.erase(id); active_workers.erase(id);
} }
@ -356,6 +421,7 @@ namespace CSP {
df::job* job = pair.second; df::job* job = pair.second;
df::unit* unit = active_workers[job->id]; df::unit* unit = active_workers[job->id];
if (!unit) continue; if (!unit) 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.monitor_active) continue;
@ -363,9 +429,7 @@ namespace CSP {
// save position // save position
if (unit->pos != job->pos && isFloorTerrain(*Maps::getTileType(unit->pos))) { if (unit->pos != job->pos && isFloorTerrain(*Maps::getTileType(unit->pos))) {
// worker is perfectly safe right now // worker is probably safe right now
last_safe[unit->id] = unit->pos;
TRACE(monitor).print(" -> save safe position\n");
continue; continue;
} }
@ -382,7 +446,9 @@ namespace CSP {
// queue digging the job instantly // queue digging the job instantly
dignow_queue.emplace(job->pos); dignow_queue.emplace(job->pos);
DEBUG(monitor).print(" -> insta-dig\n"); DEBUG(monitor).print(" -> insta-dig\n");
} else if (Maps::isValidTilePos(job->pos)) { } else if (config.resurrect) {
endangered_units.emplace(unit, tick);
} else {
// set marker mode // set marker mode
Maps::getTileOccupancy(job->pos)->bits.dig_marked = true; Maps::getTileOccupancy(job->pos)->bits.dig_marked = true;
@ -400,6 +466,13 @@ namespace CSP {
DEBUG(monitor).print(" -> set marker mode\n"); DEBUG(monitor).print(" -> set marker mode\n");
} }
} }
} else if (config.resurrect) {
resurrect(out, unit->id);
if (last_safe.count(unit->id)) {
df::coord lowest = simulate_fall(last_safe[unit->id]);
Units::teleport(unit, lowest);
}
print_res_msg(unit);
} }
} }
TRACE(monitor).print("OnUpdate() monitoring done\n"); TRACE(monitor).print("OnUpdate() monitoring done\n");
@ -408,25 +481,27 @@ namespace CSP {
// Resurrect Dead Workers // Resurrect Dead Workers
if (config.resurrect && tick - last_resurrect_tick >= 1) { if (config.resurrect && tick - last_resurrect_tick >= 1) {
last_resurrect_tick = tick; last_resurrect_tick = tick;
static std::unordered_map<df::unit*, int32_t> age;
// clean up any "endangered" workers that have been tracked 100 ticks or more // clean up any "endangered" workers that have been tracked 100 ticks or more
for (auto iter = age.begin(); iter != age.end();) { for (auto iter = endangered_units.begin(); iter != endangered_units.end();) {
if (tick - iter->second >= 1200) { //keep watch 1 day if (tick - iter->second >= 1200) { //keep watch 1 day
endangered_workers.erase(iter->first); DEBUG(plugin).print("It has been one day since [id %d]'s last incident.\n", iter->first->id);
iter = age.erase(iter); iter = endangered_units.erase(iter);
continue; continue;
} }
++iter; ++iter;
} }
// resurrect any dead units // resurrect any dead units
for (auto unit : endangered_workers) { for (auto pair : endangered_units) {
age.emplace(unit, tick); auto unit = pair.first;
if (!Units::isAlive(unit)) { if (!Units::isAlive(unit)) {
resurrect(out, unit->id); resurrect(out, unit->id);
Units::teleport(unit, last_safe[unit->id]); if (last_safe.count(unit->id)) {
WARN(plugin).print(">RESURRECTING<\n"); df::coord lowest = simulate_fall(last_safe[unit->id]);
Units::teleport(unit, lowest);
}
print_res_msg(unit);
} }
} }
} }
@ -480,32 +555,25 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
} }
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
if (enabled && World::isFortressMode() && Maps::IsValid()) { switch (event) {
switch (event) { case SC_UNPAUSED:
case SC_MAP_LOADED: if (enabled && World::isFortressMode() && Maps::IsValid()) {
CSP::active_workers.clear();
// cache the map size
Maps::getSize(mapx, mapy, mapz);
case SC_UNPAUSED:
// manage all designations on unpause // manage all designations on unpause
CSP::UnpauseEvent(); CSP::UnpauseEvent();
default: }
return DFHack::CR_OK; break;
}
}
switch (event) {
case SC_WORLD_LOADED:
case SC_WORLD_UNLOADED:
case SC_MAP_UNLOADED:
// destroy any old group data
out.print("channel-safely: unloading data!\n");
ChannelManager::Get().destroy_groups();
case SC_MAP_LOADED: case SC_MAP_LOADED:
// cache the map size // cache the map size
Maps::getSize(mapx, mapy, mapz); Maps::getSize(mapx, mapy, mapz);
case SC_WORLD_LOADED:
case SC_WORLD_UNLOADED:
case SC_MAP_UNLOADED:
CSP::ClearData();
break;
default: default:
return DFHack::CR_OK; return DFHack::CR_OK;
} }
return DFHack::CR_OK;
} }
DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event) { DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event) {