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)
: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.
Runs on the refresh cycles. (default: disabled)

@ -2,6 +2,7 @@
#include <tile-cache.h>
#include <inlines.h>
#include <modules/EventManager.h> //hash function for df::coord
#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) {
groups.remove(map_pos);
jobs.erase(map_pos);
CSP::dignow_queue.erase(map_pos);
TileCache::Get().uncache(map_pos);
}

@ -109,6 +109,33 @@ enum SettingConfigData {
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
inline bool dig_now(color_ostream &out, const df::coord &map_pos) {
auto L = Lua::Core::State;
@ -134,14 +161,24 @@ inline void resurrect(color_ostream &out, const int32_t &unit) {
}
namespace CSP {
std::unordered_set<df::unit*> endangered_workers;
std::unordered_map<df::job*, int32_t> job_ids;
std::unordered_map<df::unit*, int32_t> endangered_units;
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::unit*> active_workers;
std::unordered_map<int32_t, df::coord> last_safe;
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() {
if (pfeature.isValid() && psetting.isValid()) {
try {
@ -209,9 +246,14 @@ namespace CSP {
df::coord &pos = job->pos;
WARN(jobs).print(" -> Starting job at (" COORD ")\n", COORDARGS(pos));
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_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
TRACE(jobs).print(" setting job tile to restricted\n");
@ -244,28 +286,27 @@ namespace CSP {
ChannelManager::Get().mark_done(job->pos);
ChannelManager::Get().manage_group(below);
ChannelManager::Get().debug();
} else {
// the tile is unchanged
if (config.resurrect) {
// this is the only place we can be 100% sure of "safety"
// (excluding deadly enemies that will have arrived)
if (active_workers.count(job->id)) {
df::unit* worker = active_workers[job->id];
endangered_workers.emplace(active_workers[job->id]);
ERR(jobs).print("() -> job at (" COORD ") is done, but (" COORD ") doesn't appear done.\n",COORDARGS(worker->pos), COORDARGS(job->pos));
if (config.insta_dig) {
dignow_queue.emplace(job->pos);
last_safe[worker->id] = worker->pos;
}
}
}
// clean up
if (!config.resurrect) {
auto jp = active_jobs[job->id];
job_ids.erase(jp);
job_id_map.erase(jp);
active_workers.erase(job->id);
active_jobs.erase(job->id);
}
}
INFO(jobs).print("JobCompletedEvent() exits\n");
}
}
void NewReportEvent(color_ostream &out, void* r) {
int32_t tick = df::global::world->frame_counter;
auto report_id = (int32_t)(intptr_t(r));
if (df::global::world) {
std::vector<df::report*> &reports = df::global::world->status.reports;
@ -274,17 +315,46 @@ namespace CSP {
df::report* report = reports.at(idx);
switch (report->type) {
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 (report->text.find("Dangerous") != std::string::npos) {
if (config.insta_dig) {
if (report->text.find("cancels Dig") != std::string::npos) {
dignow_queue.emplace(report->pos);
break;
} else if (!report->flags.bits.unconscious) {
break;
} else if (report->text.find("path") != std::string::npos) {
dignow_queue.emplace(report->pos);
}
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:
for (auto p : active_workers) {
endangered_workers.emplace(p.second);
if (config.resurrect) {
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:
break;
}
@ -292,6 +362,9 @@ namespace CSP {
}
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()) {
static int32_t last_tick = df::global::world->frame_counter;
static int32_t last_monitor_tick = df::global::world->frame_counter;
@ -303,25 +376,17 @@ namespace CSP {
if (tick - last_refresh_tick >= config.refresh_freq) {
last_refresh_tick = tick;
TRACE(monitor).print("OnUpdate() refreshing now\n");
UnpauseEvent();
if (config.insta_dig) {
TRACE(monitor).print(" -> evaluate dignow queue\n");
for (auto iter = dignow_queue.begin(); iter != dignow_queue.end();) {
if (!has_unit(Maps::getTileOccupancy(*iter))) {
dig_now(out, *iter);
dig_now(out, *iter); // teleports units to the bottom of a simulated fall
iter = dignow_queue.erase(iter);
WARN(plugin).print(">INSTA-DIGGING<\n");
continue;
} else {
// todo: teleport?
//Units::teleport()
DEBUG(plugin).print(">INSTA-DIGGING<\n");
}
++iter;
}
UnpauseEvent();
TRACE(monitor).print("OnUpdate() refresh done\n");
}
}
// Clean up stale df::job*
if ((config.monitor_active || config.resurrect) && tick - last_tick >= 1) {
@ -339,8 +404,8 @@ namespace CSP {
std::unordered_set<df::job*> erase;
map_value_difference(active_jobs, valid_jobs, erase);
for (auto j : erase) {
auto id = job_ids[j];
job_ids.erase(j);
auto id = job_id_map[j];
job_id_map.erase(j);
active_jobs.erase(id);
active_workers.erase(id);
}
@ -356,6 +421,7 @@ namespace CSP {
df::job* job = pair.second;
df::unit* unit = active_workers[job->id];
if (!unit) continue;
if (!Maps::isValidTilePos(job->pos)) continue;
TRACE(monitor).print(" -> check for job in tracking\n");
if (Units::isAlive(unit)) {
if (!config.monitor_active) continue;
@ -363,9 +429,7 @@ namespace CSP {
// save position
if (unit->pos != job->pos && isFloorTerrain(*Maps::getTileType(unit->pos))) {
// worker is perfectly safe right now
last_safe[unit->id] = unit->pos;
TRACE(monitor).print(" -> save safe position\n");
// worker is probably safe right now
continue;
}
@ -382,7 +446,9 @@ namespace CSP {
// queue digging the job instantly
dignow_queue.emplace(job->pos);
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
Maps::getTileOccupancy(job->pos)->bits.dig_marked = true;
@ -400,6 +466,13 @@ namespace CSP {
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");
@ -408,25 +481,27 @@ namespace CSP {
// Resurrect Dead Workers
if (config.resurrect && tick - last_resurrect_tick >= 1) {
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
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
endangered_workers.erase(iter->first);
iter = age.erase(iter);
DEBUG(plugin).print("It has been one day since [id %d]'s last incident.\n", iter->first->id);
iter = endangered_units.erase(iter);
continue;
}
++iter;
}
// resurrect any dead units
for (auto unit : endangered_workers) {
age.emplace(unit, tick);
for (auto pair : endangered_units) {
auto unit = pair.first;
if (!Units::isAlive(unit)) {
resurrect(out, unit->id);
Units::teleport(unit, last_safe[unit->id]);
WARN(plugin).print(">RESURRECTING<\n");
if (last_safe.count(unit->id)) {
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) {
if (enabled && World::isFortressMode() && Maps::IsValid()) {
switch (event) {
case SC_MAP_LOADED:
CSP::active_workers.clear();
// cache the map size
Maps::getSize(mapx, mapy, mapz);
case SC_UNPAUSED:
if (enabled && World::isFortressMode() && Maps::IsValid()) {
// manage all designations on unpause
CSP::UnpauseEvent();
default:
return DFHack::CR_OK;
}
}
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();
break;
case SC_MAP_LOADED:
// cache the map size
Maps::getSize(mapx, mapy, mapz);
case SC_WORLD_LOADED:
case SC_WORLD_UNLOADED:
case SC_MAP_UNLOADED:
CSP::ClearData();
break;
default:
return DFHack::CR_OK;
}
return DFHack::CR_OK;
}
DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event) {