Merge branch 'develop' into plugin_preservetombs

develop
Najeeb Al-Shabibi 2023-09-30 14:43:53 +01:00
commit f77daafdd5
10 changed files with 351 additions and 128 deletions

@ -10,6 +10,13 @@ work (e.g. links from the `changelog`).
:local:
:depth: 1
.. _workorder-recheck:
workorder-recheck
=================
Tool to set 'Checking' status of the selected work order, allowing conditions to be
reevaluated. Merged into `orders`.
.. _autohauler:
autohauler

@ -54,16 +54,21 @@ Template for new versions:
## New Tools
- `preserve-tombs`: tracks tomb assignments to living units and ensures that the tomb stays assigned to them when they die.
- `tubefill`: (reinstated) replenishes mined-out adamantine
## New Features
## Fixes
- `autolabor`: now unconditionally re-enables vanilla work details when the fort or the plugin is unloaded
- ``dfhack.TranslateName()``: fixed crash on certain invalid names, which affected `warn-starving`
- EventManager: Unit death event no longer misfires on units leaving the map
## Misc Improvements
- `dig`: `digtype` command now has options to choose between designating only visible tiles or hidden tiles, as well as "auto" dig mode. Z-level options adjusted to allow choosing z-levels above, below, or the same as the cursor.
- `hotkeys`: make the DFHack logo brighten on hover in ascii mode to indicate that it is clickable
- `hotkeys`: use vertical bars instead of "!" symbols for the DFHack logo to make it easier to read
- EventManager: guarded against potential iterator invalidation if one of the event listeners modified the global data structure being iterated over
- EventManager: changed firing order of building created and building destroyed events to improve performance in the building location cache.
## Documentation

@ -17,6 +17,13 @@ Usage
manager orders. It will not clear the orders that already exist.
``orders clear``
Deletes all manager orders in the current embark.
``orders recheck [this]``
Sets the status to ``Checking`` (from ``Active``) for all work orders. if the
"this" option is passed, only sets the status for the workorder whose condition
details page is open. This makes the manager reevaluate its conditions.
This is especially useful for an order that had its conditions met when it
was started, but the requisite items have since disappeared and the workorder
is now generating job cancellation spam.
``orders sort``
Sorts current manager orders by repeat frequency so repeating orders don't
prevent one-time orders from ever being completed. The sorting order is:

@ -3,9 +3,13 @@ tubefill
.. dfhack-tool::
:summary: Replenishes mined-out adamantine.
:tags: unavailable fort armok map
:tags: fort armok map
Veins that were originally hollow will be left alone.
This tool replaces mined-out tiles of adamantine spires with fresh, undug
adamantine walls, ready to be re-harvested. Empty tiles within the spire that
used to contain special gemstones, obsidian, water, or magma will also be
replaced with fresh adamantine. Adamantine spires that were originally hollow
will be left hollow. See below for more details.
Usage
-----

@ -43,6 +43,7 @@
#include <unordered_map>
#include <unordered_set>
#include <array>
#include <utility>
namespace DFHack {
DBG_DECLARE(eventmanager, log, DebugCategory::LINFO);
@ -246,6 +247,15 @@ static int32_t reportToRelevantUnitsTime = -1;
//interaction
static int32_t lastReportInteraction;
struct hash_pair {
template<typename A, typename B>
size_t operator()(const std::pair<A,B>& p) const {
auto h1 = std::hash<A>{}(p.first);
auto h2 = std::hash<B>{}(p.second);
return h1 ^ (h2 << 1);
}
};
void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) {
static bool doOnce = false;
// const string eventNames[] = {"world loaded", "world unloaded", "map loaded", "map unloaded", "viewscreen changed", "core initialized", "begin unload", "paused", "unpaused"};
@ -276,9 +286,9 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event
gameLoaded = false;
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNLOAD].begin(), handlers[EventType::UNLOAD].end());
for (auto &key_value : copy) {
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for map unloaded state change event\n");
key_value.second.eventHandler(out, nullptr);
handle.eventHandler(out, nullptr);
}
} else if ( event == DFHack::SC_MAP_LOADED ) {
/*
@ -375,8 +385,7 @@ void DFHack::EventManager::manageEvents(color_ostream& out) {
continue;
int32_t eventFrequency = -100;
if ( a != EventType::TICK )
for (auto &key_value : handlers[a]) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : handlers[a]) {
if (handle.freq < eventFrequency || eventFrequency == -100 )
eventFrequency = handle.freq;
}
@ -439,8 +448,7 @@ static void manageJobInitiatedEvent(color_ostream& out) {
continue;
if ( link->item->id <= lastJobId )
continue;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for job initiated event\n");
handle.eventHandler(out, (void*)link->item);
}
@ -455,17 +463,24 @@ static void manageJobStartedEvent(color_ostream& out) {
static unordered_set<int32_t> startedJobs;
vector<df::job*> new_started_jobs;
// iterate event handler callbacks
multimap<Plugin*, EventHandler> copy(handlers[EventType::JOB_STARTED].begin(), handlers[EventType::JOB_STARTED].end());
for (df::job_list_link* link = df::global::world->jobs.list.next; link != nullptr; link = link->next) {
df::job* job = link->item;
for (df::job_list_link* link = &df::global::world->jobs.list; link->next != nullptr; link = link->next) {
df::job* job = link->next->item;
int32_t j_id = job->id;
if (job && Job::getWorker(job) && !startedJobs.count(job->id)) {
startedJobs.emplace(job->id);
for (auto &key_value : copy) {
auto &handler = key_value.second;
for (auto &[_,handle] : copy) {
// the jobs must have a worker to start
DEBUG(log,out).print("calling handler for job started event\n");
handler.eventHandler(out, job);
handle.eventHandler(out, job);
}
}
if (link->next == nullptr || link->next->item->id != j_id) {
if ( Once::doOnce("EventManager jobstarted job removed") ) {
out.print("%s,%d: job %u removed from jobs linked list\n", __FILE__, __LINE__, j_id);
}
}
}
@ -487,11 +502,11 @@ static void manageJobCompletedEvent(color_ostream& out) {
int32_t tick1 = df::global::world->frame_counter;
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end());
map<int32_t, df::job*> nowJobs;
unordered_map<int32_t, df::job*> nowJobs;
for ( df::job_list_link* link = &df::global::world->jobs.list; link != nullptr; link = link->next ) {
if ( link->item == nullptr )
continue;
nowJobs[link->item->id] = link->item;
nowJobs.emplace(link->item->id, link->item);
}
#if 0
@ -573,10 +588,9 @@ static void manageJobCompletedEvent(color_ostream& out) {
continue;
//still false positive if cancelled at EXACTLY the right time, but experiments show this doesn't happen
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for repeated job completed event\n");
handle.eventHandler(out, (void*)&job0);
handle.eventHandler(out, (void*) &job0);
}
continue;
}
@ -586,28 +600,27 @@ static void manageJobCompletedEvent(color_ostream& out) {
if ( job0.flags.bits.repeat || job0.completion_timer != 0 )
continue;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for job completed event\n");
handle.eventHandler(out, (void*)&job0);
handle.eventHandler(out, (void*) &job0);
}
}
//erase old jobs, copy over possibly altered jobs
for (auto &prevJob : prevJobs) {
Job::deleteJobStruct(prevJob.second, true);
for (auto &[_,prev_job] : prevJobs) {
Job::deleteJobStruct(prev_job, true);
}
prevJobs.clear();
//create new jobs
for (auto &nowJob : nowJobs) {
for (auto &[_,now_job] : nowJobs) {
/*map<int32_t, df::job*>::iterator i = prevJobs.find((*j).first);
if ( i != prevJobs.end() ) {
continue;
}*/
df::job* newJob = Job::cloneJobStruct(nowJob.second, true);
prevJobs[newJob->id] = newJob;
df::job* newJob = Job::cloneJobStruct(now_job, true);
prevJobs.emplace(newJob->id, newJob);
}
}
@ -617,16 +630,18 @@ static void manageNewUnitActiveEvent(color_ostream& out) {
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_NEW_ACTIVE].begin(), handlers[EventType::UNIT_NEW_ACTIVE].end());
// iterate event handler callbacks
for (auto &key_value : copy) {
auto &handler = key_value.second;
vector<int32_t> new_active_unit_ids;
for (df::unit* unit : df::global::world->units.active) {
int32_t id = unit->id;
if (!activeUnits.count(id)) {
activeUnits.emplace(id);
DEBUG(log,out).print("calling handler for new unit event\n");
handler.eventHandler(out, (void*) intptr_t(id)); // intptr_t() avoids cast from smaller type warning
if (!activeUnits.count(unit->id)) {
activeUnits.emplace(unit->id);
new_active_unit_ids.emplace_back(unit->id);
}
}
for (int32_t unit_id : new_active_unit_ids) {
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for new unit event\n");
handle.eventHandler(out, (void*) intptr_t(unit_id)); // intptr_t() avoids cast from smaller type warning
}
}
}
@ -635,22 +650,26 @@ static void manageUnitDeathEvent(color_ostream& out) {
if (!df::global::world)
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end());
vector<int32_t> dead_unit_ids;
for (auto unit : df::global::world->units.all) {
//if ( unit->counters.death_id == -1 ) {
if ( Units::isActive(unit) ) {
livingUnits.insert(unit->id);
continue;
}
if (!Units::isDead(unit)) continue; // for units that have left the map but aren't dead
//dead: if dead since last check, trigger events
if ( livingUnits.find(unit->id) == livingUnits.end() )
continue;
livingUnits.erase(unit->id);
dead_unit_ids.emplace_back(unit->id);
}
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (int32_t unit_id : dead_unit_ids) {
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit death event\n");
handle.eventHandler(out, (void*)intptr_t(unit->id));
handle.eventHandler(out, (void*)intptr_t(unit_id));
}
livingUnits.erase(unit->id);
}
}
@ -666,6 +685,8 @@ static void manageItemCreationEvent(color_ostream& out) {
multimap<Plugin*,EventHandler> copy(handlers[EventType::ITEM_CREATED].begin(), handlers[EventType::ITEM_CREATED].end());
size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false);
if ( index != 0 ) index--;
std::vector<int32_t> created_items;
for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) {
df::item* item = df::global::world->items.all[a];
//already processed
@ -683,12 +704,17 @@ static void manageItemCreationEvent(color_ostream& out) {
//spider webs don't count
if ( item->flags.bits.spider_web )
continue;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
created_items.push_back(item->id);
}
// handle all created items
for (int32_t item_id : created_items) {
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for item created event\n");
handle.eventHandler(out, (void*)intptr_t(item->id));
handle.eventHandler(out, (void*)intptr_t(item_id));
}
}
nextItem = *df::global::item_next_id;
}
@ -703,6 +729,7 @@ static void manageBuildingEvent(color_ostream& out) {
**/
multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end());
//first alert people about new buildings
vector<int32_t> new_buildings;
for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) {
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a);
if ( index == -1 ) {
@ -711,30 +738,34 @@ static void manageBuildingEvent(color_ostream& out) {
continue;
}
buildings.insert(a);
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for created building event\n");
handle.eventHandler(out, (void*)intptr_t(a));
}
new_buildings.emplace_back(a);
}
nextBuilding = *df::global::building_next_id;
//now alert people about destroyed buildings
for ( auto a = buildings.begin(); a != buildings.end(); ) {
int32_t id = *a;
for ( auto it = buildings.begin(); it != buildings.end(); ) {
int32_t id = *it;
int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id);
if ( index != -1 ) {
a++;
++it;
continue;
}
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for destroyed building event\n");
handle.eventHandler(out, (void*)intptr_t(id));
}
a = buildings.erase(a);
it = buildings.erase(it);
}
//alert people about newly created buildings
std::for_each(new_buildings.begin(), new_buildings.end(), [&](int32_t building){
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for created building event\n");
handle.eventHandler(out, (void*)intptr_t(building));
}
});
}
static void manageConstructionEvent(color_ostream& out) {
@ -743,37 +774,43 @@ static void manageConstructionEvent(color_ostream& out) {
//unordered_set<df::construction*> constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end());
multimap<Plugin*, EventHandler> copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end());
// find & send construction removals
for (auto iter = constructions.begin(); iter != constructions.end();) {
auto &construction = *iter;
// if we can't find it, it was removed
if (df::construction::find(construction.pos) != nullptr) {
++iter;
continue;
unordered_set<df::construction> next_construction_set; // will be swapped with constructions
next_construction_set.reserve(constructions.bucket_count());
vector<df::construction> new_constructions;
// find new constructions - swapping found constructions over from constructions to next_construction_set
for (auto c : df::global::world->constructions) {
auto &construction = *c;
auto it = constructions.find(construction);
if (it == constructions.end()) {
// handle new construction event later
new_constructions.emplace_back(construction);
}
else {
constructions.erase(it);
}
// send construction to handlers, because it was removed
for (const auto &key_value: copy) {
EventHandler handle = key_value.second;
next_construction_set.emplace(construction);
}
constructions.swap(next_construction_set);
// now next_construction_set contains all the constructions that were removed (not found in df::global::world->constructions)
for (auto& construction : next_construction_set) {
// handle construction removed event
for (const auto &[_,handle]: copy) {
DEBUG(log,out).print("calling handler for destroyed construction event\n");
handle.eventHandler(out, (void*) &construction);
}
// erase from existent constructions
iter = constructions.erase(iter);
}
// find & send construction additions
for (auto c: df::global::world->constructions) {
auto &construction = *c;
// add construction to constructions, if it isn't already present
if (constructions.emplace(construction).second) {
// send construction to handlers, because it is new
for (const auto &key_value: copy) {
EventHandler handle = key_value.second;
// now handle all the new constructions
for (auto& construction : new_constructions) {
for (const auto &[_,handle]: copy) {
DEBUG(log,out).print("calling handler for created construction event\n");
handle.eventHandler(out, (void*) &construction);
}
}
}
}
static void manageSyndromeEvent(color_ostream& out) {
@ -781,6 +818,8 @@ static void manageSyndromeEvent(color_ostream& out) {
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end());
int32_t highestTime = -1;
std::vector<SyndromeData> new_syndrome_data;
for (auto unit : df::global::world->units.all) {
/*
@ -795,14 +834,16 @@ static void manageSyndromeEvent(color_ostream& out) {
if ( startTime <= lastSyndromeTime )
continue;
SyndromeData data(unit->id, b);
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
new_syndrome_data.emplace_back(unit->id, b);
}
}
for (auto& data : new_syndrome_data) {
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for syndrome event\n");
handle.eventHandler(out, (void*)&data);
}
}
}
lastSyndromeTime = highestTime;
}
@ -815,8 +856,7 @@ static void manageInvasionEvent(color_ostream& out) {
return;
nextInvasion = df::global::plotinfo->invasions.next_id;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for invasion event\n");
handle.eventHandler(out, (void*)intptr_t(nextInvasion-1));
}
@ -829,6 +869,18 @@ static void manageEquipmentEvent(color_ostream& out) {
unordered_map<int32_t, InventoryItem> itemIdToInventoryItem;
unordered_set<int32_t> currentlyEquipped;
vector<InventoryChangeData> equipment_pickups;
vector<InventoryChangeData> equipment_drops;
vector<InventoryChangeData> equipment_changes;
// This vector stores the pointers to newly created changed items
// needed as the stack allocated temporary (in the loop) is lost when we go to
// handle the event calls, so we move that data to the heap if its needed,
// and then once we are done we delete everything.
vector<InventoryItem*> changed_items;
for (auto unit : df::global::world->units.all) {
itemIdToInventoryItem.clear();
currentlyEquipped.clear();
@ -856,40 +908,30 @@ static void manageEquipmentEvent(color_ostream& out) {
auto c = itemIdToInventoryItem.find(dfitem_new->item->id);
if ( c == itemIdToInventoryItem.end() ) {
//new item equipped (probably just picked up)
InventoryChangeData data(unit->id, nullptr, &item_new);
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for new item equipped inventory change event\n");
handle.eventHandler(out, (void*)&data);
}
changed_items.emplace_back(new InventoryItem(item_new));
equipment_pickups.emplace_back(unit->id, nullptr, changed_items.back());
continue;
}
InventoryItem item_old = (*c).second;
InventoryItem item_old = c->second;
df::unit_inventory_item& item0 = item_old.item;
df::unit_inventory_item& item1 = item_new.item;
if ( item0.mode == item1.mode && item0.body_part_id == item1.body_part_id && item0.wound_id == item1.wound_id )
continue;
//some sort of change in how it's equipped
InventoryChangeData data(unit->id, &item_old, &item_new);
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for inventory change event\n");
handle.eventHandler(out, (void*)&data);
}
changed_items.emplace_back(new InventoryItem(item_new));
InventoryItem* item_new_ptr = changed_items.back();
changed_items.emplace_back(new InventoryItem(item_old));
InventoryItem* item_old_ptr = changed_items.back();
equipment_changes.emplace_back(unit->id, item_old_ptr, item_new_ptr);
}
//check for dropped items
for (auto i : v) {
if ( currentlyEquipped.find(i.itemId) != currentlyEquipped.end() )
continue;
//TODO: delete ptr if invalid
InventoryChangeData data(unit->id, &i, nullptr);
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for dropped item inventory change event\n");
handle.eventHandler(out, (void*)&data);
}
changed_items.emplace_back(new InventoryItem(i));
equipment_drops.emplace_back(unit->id, changed_items.back(), nullptr);
}
if ( !hadEquipment )
delete temp;
@ -902,6 +944,31 @@ static void manageEquipmentEvent(color_ostream& out) {
equipment.push_back(item);
}
}
// now handle events
std::for_each(equipment_pickups.begin(), equipment_pickups.end(), [&](InventoryChangeData& data) {
for (auto &[_, handle] : copy) {
DEBUG(log,out).print("calling handler for new item equipped inventory change event\n");
handle.eventHandler(out, (void*) &data);
}
});
std::for_each(equipment_drops.begin(), equipment_drops.end(), [&](InventoryChangeData& data) {
for (auto &[_, handle] : copy) {
DEBUG(log,out).print("calling handler for dropped item inventory change event\n");
handle.eventHandler(out, (void*) &data);
}
});
std::for_each(equipment_changes.begin(), equipment_changes.end(), [&](InventoryChangeData& data) {
for (auto &[_, handle] : copy) {
DEBUG(log,out).print("calling handler for inventory change event\n");
handle.eventHandler(out, (void*) &data);
}
});
// clean up changed items list
std::for_each(changed_items.begin(), changed_items.end(), [](InventoryItem* p){
delete p;
});
}
static void updateReportToRelevantUnits() {
@ -939,8 +1006,7 @@ static void manageReportEvent(color_ostream& out) {
for ( ; idx < reports.size(); idx++ ) {
df::report* report = reports[idx];
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for report event\n");
handle.eventHandler(out, (void*)intptr_t(report->id));
}
@ -981,7 +1047,7 @@ static void manageUnitAttackEvent(color_ostream& out) {
if ( strikeReports.empty() )
return;
updateReportToRelevantUnits();
map<int32_t, map<int32_t, int32_t> > alreadyDone;
unordered_set<std::pair<int32_t, int32_t>, hash_pair> already_done;
for (int reportId : strikeReports) {
df::report* report = df::report::find(reportId);
if ( !report )
@ -1011,27 +1077,25 @@ static void manageUnitAttackEvent(color_ostream& out) {
UnitAttackData data{};
data.report_id = report->id;
if ( wound1 && !alreadyDone[unit1->id][unit2->id] ) {
if ( wound1 && already_done.find(std::make_pair(unit1->id,unit2->id)) == already_done.end() ) {
data.attacker = unit1->id;
data.defender = unit2->id;
data.wound = wound1->id;
alreadyDone[data.attacker][data.defender] = 1;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
already_done.emplace(unit1->id, unit2->id);
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit1 attack unit attack event\n");
handle.eventHandler(out, (void*)&data);
}
}
if ( wound2 && !alreadyDone[unit1->id][unit2->id] ) {
if ( wound2 && already_done.find(std::make_pair(unit1->id,unit2->id)) == already_done.end() ) {
data.attacker = unit2->id;
data.defender = unit1->id;
data.wound = wound2->id;
alreadyDone[data.attacker][data.defender] = 1;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
already_done.emplace(unit1->id, unit2->id);
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit2 attack unit attack event\n");
handle.eventHandler(out, (void*)&data);
}
@ -1041,9 +1105,9 @@ static void manageUnitAttackEvent(color_ostream& out) {
data.attacker = unit2->id;
data.defender = unit1->id;
data.wound = -1;
alreadyDone[data.attacker][data.defender] = 1;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
already_done.emplace(unit1->id, unit2->id);
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit1 killed unit attack event\n");
handle.eventHandler(out, (void*)&data);
}
@ -1053,9 +1117,9 @@ static void manageUnitAttackEvent(color_ostream& out) {
data.attacker = unit1->id;
data.defender = unit2->id;
data.wound = -1;
alreadyDone[data.attacker][data.defender] = 1;
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
already_done.emplace(unit1->id, unit2->id);
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit2 killed unit attack event\n");
handle.eventHandler(out, (void*)&data);
}
@ -1313,8 +1377,7 @@ static void manageInteractionEvent(color_ostream& out) {
lastAttacker = df::unit::find(data.attacker);
//lastDefender = df::unit::find(data.defender);
//fire event
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for interaction event\n");
handle.eventHandler(out, (void*)&data);
}

@ -1 +1 @@
Subproject commit 9312906c5a33feea50c0c32cd683ad22fb87822c
Subproject commit dc1d5c80f03a68f6d58ff4f8dbbdf6855a7e9781

@ -168,7 +168,7 @@ if(BUILD_SUPPORTED)
dfhack_plugin(tiletypes tiletypes.cpp Brushes.h LINK_LIBRARIES lua)
#dfhack_plugin(title-folder title-folder.cpp)
#dfhack_plugin(trackstop trackstop.cpp)
#dfhack_plugin(tubefill tubefill.cpp)
dfhack_plugin(tubefill tubefill.cpp)
#add_subdirectory(tweak)
dfhack_plugin(workflow workflow.cpp LINK_LIBRARIES lua)
dfhack_plugin(work-now work-now.cpp)

@ -62,12 +62,16 @@ local function do_export()
}:show()
end
local function do_recheck()
dfhack.run_command('orders', 'recheck')
end
OrdersOverlay = defclass(OrdersOverlay, overlay.OverlayWidget)
OrdersOverlay.ATTRS{
default_pos={x=53,y=-6},
default_enabled=true,
viewscreens='dwarfmode/Info/WORK_ORDERS/Default',
frame={w=30, h=4},
frame={w=46, h=4},
}
function OrdersOverlay:init()
@ -95,13 +99,20 @@ function OrdersOverlay:init()
},
widgets.HotkeyLabel{
frame={t=0, l=15},
label='recheck',
key='CUSTOM_CTRL_R',
auto_width=true,
on_activate=do_recheck,
},
widgets.HotkeyLabel{
frame={t=1, l=15},
label='sort',
key='CUSTOM_CTRL_O',
auto_width=true,
on_activate=do_sort,
},
widgets.HotkeyLabel{
frame={t=1, l=15},
frame={t=0, l=31},
label='clear',
key='CUSTOM_CTRL_C',
auto_width=true,
@ -157,7 +168,91 @@ function OrdersOverlay:render(dc)
OrdersOverlay.super.render(self, dc)
end
-- Resets the selected work order to the `Checking` state
local function set_current_inactive()
local scrConditions = df.global.game.main_interface.info.work_orders.conditions
if scrConditions.open then
dfhack.run_command('orders', 'recheck', 'this')
else
qerror("Order conditions is not open")
end
end
local function is_current_active()
local scrConditions = df.global.game.main_interface.info.work_orders.conditions
local order = scrConditions.wq
return order.status.active
end
-- -------------------
-- RecheckOverlay
--
local focusString = 'dwarfmode/Info/WORK_ORDERS/Conditions'
RecheckOverlay = defclass(RecheckOverlay, overlay.OverlayWidget)
RecheckOverlay.ATTRS{
default_pos={x=6,y=8},
default_enabled=true,
viewscreens=focusString,
-- width is the sum of lengths of `[` + `Ctrl+A` + `: ` + button.label + `]`
frame={w=1 + 6 + 2 + 16 + 1, h=3},
}
local function areTabsInTwoRows()
-- get the tile above the order status icon
local pen = dfhack.screen.readTile(7, 7, false)
-- in graphics mode, `0` when one row, something else when two (`67` aka 'C' from "Creatures")
-- in ASCII mode, `32` aka ' ' when one row, something else when two (`196` aka '-' from tab frame's top)
return (pen.ch ~= 0 and pen.ch ~= 32)
end
function RecheckOverlay:updateTextButtonFrame()
local twoRows = areTabsInTwoRows()
if (self._twoRows == twoRows) then return false end
self._twoRows = twoRows
local frame = twoRows
and {b=0, l=0, r=0, h=1}
or {t=0, l=0, r=0, h=1}
self.subviews.button.frame = frame
return true
end
function RecheckOverlay:init()
self:addviews{
widgets.TextButton{
view_id = 'button',
-- frame={t=0, l=0, r=0, h=1}, -- is set in `updateTextButtonFrame()`
label='request re-check',
key='CUSTOM_CTRL_A',
on_activate=set_current_inactive,
enabled=is_current_active,
},
}
self:updateTextButtonFrame()
end
function RecheckOverlay:onRenderBody(dc)
if (self.frame_rect.y1 == 7) then
-- only apply this logic if the overlay is on the same row as
-- originally thought: just above the order status icon
if self:updateTextButtonFrame() then
self:updateLayout()
end
end
RecheckOverlay.super.onRenderBody(self, dc)
end
-- -------------------
OVERLAY_WIDGETS = {
recheck=RecheckOverlay,
overlay=OrdersOverlay,
}

@ -10,6 +10,7 @@
#include "json/json.h"
#include "df/building.h"
#include "df/gamest.h"
#include "df/historical_figure.h"
#include "df/itemdef_ammost.h"
#include "df/itemdef_armorst.h"
@ -36,6 +37,8 @@
using namespace DFHack;
using namespace df::enums;
using df::global::game;
DFHACK_PLUGIN("orders");
REQUIRE_GLOBAL(world);
@ -64,6 +67,8 @@ static command_result orders_export_command(color_ostream & out, const std::stri
static command_result orders_import_command(color_ostream & out, const std::string & name);
static command_result orders_clear_command(color_ostream & out);
static command_result orders_sort_command(color_ostream & out);
static command_result orders_recheck_command(color_ostream & out);
static command_result orders_recheck_current_command(color_ostream & out);
static command_result orders_command(color_ostream & out, std::vector<std::string> & parameters)
{
@ -111,6 +116,19 @@ static command_result orders_command(color_ostream & out, std::vector<std::strin
return orders_sort_command(out);
}
if (parameters[0] == "recheck" && parameters.size() == 1)
{
return orders_recheck_command(out);
}
if (parameters[0] == "recheck" && parameters.size() == 2)
{
if (parameters[1] == "this")
{
return orders_recheck_current_command(out);
}
}
return CR_WRONG_USAGE;
}
@ -1015,3 +1033,27 @@ static command_result orders_sort_command(color_ostream & out)
return CR_OK;
}
static command_result orders_recheck_command(color_ostream & out)
{
for (auto it : world->manager_orders)
{
it->status.bits.active = false;
it->status.bits.validated = false;
}
return CR_OK;
}
static command_result orders_recheck_current_command(color_ostream & out)
{
if (game->main_interface.info.work_orders.conditions.open)
{
game->main_interface.info.work_orders.conditions.wq->status.bits.active = false;
}
else
{
out << COLOR_LIGHTRED << "Order conditions is not open" << std::endl;
return CR_FAILURE;
}
return CR_OK;
}

@ -1 +1 @@
Subproject commit 76f5a2b101f0817857c9dfa8dd3d9f076137aafc
Subproject commit 57c859394936f9183d95426f75a5d3e9b305dd2a