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: :local:
:depth: 1 :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:
autohauler autohauler

@ -54,16 +54,21 @@ Template for new versions:
## New Tools ## New Tools
- `preserve-tombs`: tracks tomb assignments to living units and ensures that the tomb stays assigned to them when they die. - `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 ## New Features
## Fixes ## Fixes
- `autolabor`: now unconditionally re-enables vanilla work details when the fort or the plugin is unloaded - `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` - ``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 ## 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. - `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`: 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 - `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 ## Documentation

@ -17,6 +17,13 @@ Usage
manager orders. It will not clear the orders that already exist. manager orders. It will not clear the orders that already exist.
``orders clear`` ``orders clear``
Deletes all manager orders in the current embark. 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`` ``orders sort``
Sorts current manager orders by repeat frequency so repeating orders don't Sorts current manager orders by repeat frequency so repeating orders don't
prevent one-time orders from ever being completed. The sorting order is: prevent one-time orders from ever being completed. The sorting order is:

@ -3,9 +3,13 @@ tubefill
.. dfhack-tool:: .. dfhack-tool::
:summary: Replenishes mined-out adamantine. :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 Usage
----- -----

@ -43,6 +43,7 @@
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <array> #include <array>
#include <utility>
namespace DFHack { namespace DFHack {
DBG_DECLARE(eventmanager, log, DebugCategory::LINFO); DBG_DECLARE(eventmanager, log, DebugCategory::LINFO);
@ -246,6 +247,15 @@ static int32_t reportToRelevantUnitsTime = -1;
//interaction //interaction
static int32_t lastReportInteraction; 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) { void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) {
static bool doOnce = false; static bool doOnce = false;
// const string eventNames[] = {"world loaded", "world unloaded", "map loaded", "map unloaded", "viewscreen changed", "core initialized", "begin unload", "paused", "unpaused"}; // 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; gameLoaded = false;
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNLOAD].begin(), handlers[EventType::UNLOAD].end()); 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"); 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 ) { } else if ( event == DFHack::SC_MAP_LOADED ) {
/* /*
@ -375,8 +385,7 @@ void DFHack::EventManager::manageEvents(color_ostream& out) {
continue; continue;
int32_t eventFrequency = -100; int32_t eventFrequency = -100;
if ( a != EventType::TICK ) if ( a != EventType::TICK )
for (auto &key_value : handlers[a]) { for (auto &[_,handle] : handlers[a]) {
EventHandler &handle = key_value.second;
if (handle.freq < eventFrequency || eventFrequency == -100 ) if (handle.freq < eventFrequency || eventFrequency == -100 )
eventFrequency = handle.freq; eventFrequency = handle.freq;
} }
@ -439,8 +448,7 @@ static void manageJobInitiatedEvent(color_ostream& out) {
continue; continue;
if ( link->item->id <= lastJobId ) if ( link->item->id <= lastJobId )
continue; continue;
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for job initiated event\n"); DEBUG(log,out).print("calling handler for job initiated event\n");
handle.eventHandler(out, (void*)link->item); handle.eventHandler(out, (void*)link->item);
} }
@ -455,17 +463,24 @@ static void manageJobStartedEvent(color_ostream& out) {
static unordered_set<int32_t> startedJobs; static unordered_set<int32_t> startedJobs;
vector<df::job*> new_started_jobs;
// iterate event handler callbacks // iterate event handler callbacks
multimap<Plugin*, EventHandler> copy(handlers[EventType::JOB_STARTED].begin(), handlers[EventType::JOB_STARTED].end()); 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)) { if (job && Job::getWorker(job) && !startedJobs.count(job->id)) {
startedJobs.emplace(job->id); startedJobs.emplace(job->id);
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
auto &handler = key_value.second;
// the jobs must have a worker to start // the jobs must have a worker to start
DEBUG(log,out).print("calling handler for job started event\n"); 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; int32_t tick1 = df::global::world->frame_counter;
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end()); 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 ) { for ( df::job_list_link* link = &df::global::world->jobs.list; link != nullptr; link = link->next ) {
if ( link->item == nullptr ) if ( link->item == nullptr )
continue; continue;
nowJobs[link->item->id] = link->item; nowJobs.emplace(link->item->id, link->item);
} }
#if 0 #if 0
@ -573,10 +588,9 @@ static void manageJobCompletedEvent(color_ostream& out) {
continue; continue;
//still false positive if cancelled at EXACTLY the right time, but experiments show this doesn't happen //still false positive if cancelled at EXACTLY the right time, but experiments show this doesn't happen
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for repeated job completed event\n"); DEBUG(log,out).print("calling handler for repeated job completed event\n");
handle.eventHandler(out, (void*)&job0); handle.eventHandler(out, (void*) &job0);
} }
continue; continue;
} }
@ -586,28 +600,27 @@ static void manageJobCompletedEvent(color_ostream& out) {
if ( job0.flags.bits.repeat || job0.completion_timer != 0 ) if ( job0.flags.bits.repeat || job0.completion_timer != 0 )
continue; continue;
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for job completed event\n"); 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 //erase old jobs, copy over possibly altered jobs
for (auto &prevJob : prevJobs) { for (auto &[_,prev_job] : prevJobs) {
Job::deleteJobStruct(prevJob.second, true); Job::deleteJobStruct(prev_job, true);
} }
prevJobs.clear(); prevJobs.clear();
//create new jobs //create new jobs
for (auto &nowJob : nowJobs) { for (auto &[_,now_job] : nowJobs) {
/*map<int32_t, df::job*>::iterator i = prevJobs.find((*j).first); /*map<int32_t, df::job*>::iterator i = prevJobs.find((*j).first);
if ( i != prevJobs.end() ) { if ( i != prevJobs.end() ) {
continue; continue;
}*/ }*/
df::job* newJob = Job::cloneJobStruct(nowJob.second, true); df::job* newJob = Job::cloneJobStruct(now_job, true);
prevJobs[newJob->id] = newJob; prevJobs.emplace(newJob->id, newJob);
} }
} }
@ -617,15 +630,17 @@ static void manageNewUnitActiveEvent(color_ostream& out) {
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_NEW_ACTIVE].begin(), handlers[EventType::UNIT_NEW_ACTIVE].end()); multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_NEW_ACTIVE].begin(), handlers[EventType::UNIT_NEW_ACTIVE].end());
// iterate event handler callbacks // iterate event handler callbacks
for (auto &key_value : copy) { vector<int32_t> new_active_unit_ids;
auto &handler = key_value.second; for (df::unit* unit : df::global::world->units.active) {
for (df::unit* unit : df::global::world->units.active) { if (!activeUnits.count(unit->id)) {
int32_t id = unit->id; activeUnits.emplace(unit->id);
if (!activeUnits.count(id)) { new_active_unit_ids.emplace_back(unit->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 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) if (!df::global::world)
return; return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end()); 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) { for (auto unit : df::global::world->units.all) {
//if ( unit->counters.death_id == -1 ) { //if ( unit->counters.death_id == -1 ) {
if ( Units::isActive(unit) ) { if ( Units::isActive(unit) ) {
livingUnits.insert(unit->id); livingUnits.insert(unit->id);
continue; 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 //dead: if dead since last check, trigger events
if ( livingUnits.find(unit->id) == livingUnits.end() ) if ( livingUnits.find(unit->id) == livingUnits.end() )
continue; continue;
livingUnits.erase(unit->id);
dead_unit_ids.emplace_back(unit->id);
}
for (auto &key_value : copy) { for (int32_t unit_id : dead_unit_ids) {
EventHandler &handle = key_value.second; for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit death event\n"); 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()); 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); size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false);
if ( index != 0 ) index--; if ( index != 0 ) index--;
std::vector<int32_t> created_items;
for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) { for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) {
df::item* item = df::global::world->items.all[a]; df::item* item = df::global::world->items.all[a];
//already processed //already processed
@ -683,12 +704,17 @@ static void manageItemCreationEvent(color_ostream& out) {
//spider webs don't count //spider webs don't count
if ( item->flags.bits.spider_web ) if ( item->flags.bits.spider_web )
continue; continue;
for (auto &key_value : copy) { created_items.push_back(item->id);
EventHandler &handle = key_value.second; }
// 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"); 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; 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()); multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end());
//first alert people about new buildings //first alert people about new buildings
vector<int32_t> new_buildings;
for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) { 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); int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a);
if ( index == -1 ) { if ( index == -1 ) {
@ -711,30 +738,34 @@ static void manageBuildingEvent(color_ostream& out) {
continue; continue;
} }
buildings.insert(a); buildings.insert(a);
for (auto &key_value : copy) { new_buildings.emplace_back(a);
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for created building event\n");
handle.eventHandler(out, (void*)intptr_t(a));
}
} }
nextBuilding = *df::global::building_next_id; nextBuilding = *df::global::building_next_id;
//now alert people about destroyed buildings //now alert people about destroyed buildings
for ( auto a = buildings.begin(); a != buildings.end(); ) { for ( auto it = buildings.begin(); it != buildings.end(); ) {
int32_t id = *a; int32_t id = *it;
int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id); int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id);
if ( index != -1 ) { if ( index != -1 ) {
a++; ++it;
continue; continue;
} }
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for destroyed building event\n"); DEBUG(log,out).print("calling handler for destroyed building event\n");
handle.eventHandler(out, (void*)intptr_t(id)); 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) { static void manageConstructionEvent(color_ostream& out) {
@ -743,35 +774,41 @@ static void manageConstructionEvent(color_ostream& out) {
//unordered_set<df::construction*> constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end()); //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()); multimap<Plugin*, EventHandler> copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end());
// find & send construction removals
for (auto iter = constructions.begin(); iter != constructions.end();) { unordered_set<df::construction> next_construction_set; // will be swapped with constructions
auto &construction = *iter; next_construction_set.reserve(constructions.bucket_count());
// if we can't find it, it was removed vector<df::construction> new_constructions;
if (df::construction::find(construction.pos) != nullptr) {
++iter; // find new constructions - swapping found constructions over from constructions to next_construction_set
continue; 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 next_construction_set.emplace(construction);
for (const auto &key_value: copy) { }
EventHandler handle = key_value.second;
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"); DEBUG(log,out).print("calling handler for destroyed construction event\n");
handle.eventHandler(out, (void*) &construction); handle.eventHandler(out, (void*) &construction);
} }
// erase from existent constructions
iter = constructions.erase(iter);
} }
// find & send construction additions // now handle all the new constructions
for (auto c: df::global::world->constructions) { for (auto& construction : new_constructions) {
auto &construction = *c; for (const auto &[_,handle]: copy) {
// add construction to constructions, if it isn't already present DEBUG(log,out).print("calling handler for created construction event\n");
if (constructions.emplace(construction).second) { handle.eventHandler(out, (void*) &construction);
// send construction to handlers, because it is new
for (const auto &key_value: copy) {
EventHandler handle = key_value.second;
DEBUG(log,out).print("calling handler for created construction event\n");
handle.eventHandler(out, (void*) &construction);
}
} }
} }
} }
@ -781,6 +818,8 @@ static void manageSyndromeEvent(color_ostream& out) {
return; return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end()); multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end());
int32_t highestTime = -1; int32_t highestTime = -1;
std::vector<SyndromeData> new_syndrome_data;
for (auto unit : df::global::world->units.all) { for (auto unit : df::global::world->units.all) {
/* /*
@ -795,14 +834,16 @@ static void manageSyndromeEvent(color_ostream& out) {
if ( startTime <= lastSyndromeTime ) if ( startTime <= lastSyndromeTime )
continue; continue;
SyndromeData data(unit->id, b); new_syndrome_data.emplace_back(unit->id, b);
for (auto &key_value : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for syndrome event\n");
handle.eventHandler(out, (void*)&data);
}
} }
} }
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; lastSyndromeTime = highestTime;
} }
@ -815,8 +856,7 @@ static void manageInvasionEvent(color_ostream& out) {
return; return;
nextInvasion = df::global::plotinfo->invasions.next_id; nextInvasion = df::global::plotinfo->invasions.next_id;
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for invasion event\n"); DEBUG(log,out).print("calling handler for invasion event\n");
handle.eventHandler(out, (void*)intptr_t(nextInvasion-1)); 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_map<int32_t, InventoryItem> itemIdToInventoryItem;
unordered_set<int32_t> currentlyEquipped; 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) { for (auto unit : df::global::world->units.all) {
itemIdToInventoryItem.clear(); itemIdToInventoryItem.clear();
currentlyEquipped.clear(); currentlyEquipped.clear();
@ -856,40 +908,30 @@ static void manageEquipmentEvent(color_ostream& out) {
auto c = itemIdToInventoryItem.find(dfitem_new->item->id); auto c = itemIdToInventoryItem.find(dfitem_new->item->id);
if ( c == itemIdToInventoryItem.end() ) { if ( c == itemIdToInventoryItem.end() ) {
//new item equipped (probably just picked up) //new item equipped (probably just picked up)
InventoryChangeData data(unit->id, nullptr, &item_new); changed_items.emplace_back(new InventoryItem(item_new));
for (auto &key_value : copy) { equipment_pickups.emplace_back(unit->id, nullptr, changed_items.back());
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for new item equipped inventory change event\n");
handle.eventHandler(out, (void*)&data);
}
continue; continue;
} }
InventoryItem item_old = (*c).second; InventoryItem item_old = c->second;
df::unit_inventory_item& item0 = item_old.item; df::unit_inventory_item& item0 = item_old.item;
df::unit_inventory_item& item1 = item_new.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 ) if ( item0.mode == item1.mode && item0.body_part_id == item1.body_part_id && item0.wound_id == item1.wound_id )
continue; continue;
//some sort of change in how it's equipped //some sort of change in how it's equipped
changed_items.emplace_back(new InventoryItem(item_new));
InventoryChangeData data(unit->id, &item_old, &item_new); InventoryItem* item_new_ptr = changed_items.back();
for (auto &key_value : copy) { changed_items.emplace_back(new InventoryItem(item_old));
EventHandler &handle = key_value.second; InventoryItem* item_old_ptr = changed_items.back();
DEBUG(log,out).print("calling handler for inventory change event\n"); equipment_changes.emplace_back(unit->id, item_old_ptr, item_new_ptr);
handle.eventHandler(out, (void*)&data);
}
} }
//check for dropped items //check for dropped items
for (auto i : v) { for (auto i : v) {
if ( currentlyEquipped.find(i.itemId) != currentlyEquipped.end() ) if ( currentlyEquipped.find(i.itemId) != currentlyEquipped.end() )
continue; continue;
//TODO: delete ptr if invalid //TODO: delete ptr if invalid
InventoryChangeData data(unit->id, &i, nullptr); changed_items.emplace_back(new InventoryItem(i));
for (auto &key_value : copy) { equipment_drops.emplace_back(unit->id, changed_items.back(), nullptr);
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for dropped item inventory change event\n");
handle.eventHandler(out, (void*)&data);
}
} }
if ( !hadEquipment ) if ( !hadEquipment )
delete temp; delete temp;
@ -902,6 +944,31 @@ static void manageEquipmentEvent(color_ostream& out) {
equipment.push_back(item); 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() { static void updateReportToRelevantUnits() {
@ -939,8 +1006,7 @@ static void manageReportEvent(color_ostream& out) {
for ( ; idx < reports.size(); idx++ ) { for ( ; idx < reports.size(); idx++ ) {
df::report* report = reports[idx]; df::report* report = reports[idx];
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for report event\n"); DEBUG(log,out).print("calling handler for report event\n");
handle.eventHandler(out, (void*)intptr_t(report->id)); handle.eventHandler(out, (void*)intptr_t(report->id));
} }
@ -981,7 +1047,7 @@ static void manageUnitAttackEvent(color_ostream& out) {
if ( strikeReports.empty() ) if ( strikeReports.empty() )
return; return;
updateReportToRelevantUnits(); 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) { for (int reportId : strikeReports) {
df::report* report = df::report::find(reportId); df::report* report = df::report::find(reportId);
if ( !report ) if ( !report )
@ -1011,27 +1077,25 @@ static void manageUnitAttackEvent(color_ostream& out) {
UnitAttackData data{}; UnitAttackData data{};
data.report_id = report->id; 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.attacker = unit1->id;
data.defender = unit2->id; data.defender = unit2->id;
data.wound = wound1->id; data.wound = wound1->id;
alreadyDone[data.attacker][data.defender] = 1; already_done.emplace(unit1->id, unit2->id);
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for unit1 attack unit attack event\n"); DEBUG(log,out).print("calling handler for unit1 attack unit attack event\n");
handle.eventHandler(out, (void*)&data); 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.attacker = unit2->id;
data.defender = unit1->id; data.defender = unit1->id;
data.wound = wound2->id; data.wound = wound2->id;
alreadyDone[data.attacker][data.defender] = 1; already_done.emplace(unit1->id, unit2->id);
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for unit2 attack unit attack event\n"); DEBUG(log,out).print("calling handler for unit2 attack unit attack event\n");
handle.eventHandler(out, (void*)&data); handle.eventHandler(out, (void*)&data);
} }
@ -1041,9 +1105,9 @@ static void manageUnitAttackEvent(color_ostream& out) {
data.attacker = unit2->id; data.attacker = unit2->id;
data.defender = unit1->id; data.defender = unit1->id;
data.wound = -1; data.wound = -1;
alreadyDone[data.attacker][data.defender] = 1;
for (auto &key_value : copy) { already_done.emplace(unit1->id, unit2->id);
EventHandler &handle = key_value.second; for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit1 killed unit attack event\n"); DEBUG(log,out).print("calling handler for unit1 killed unit attack event\n");
handle.eventHandler(out, (void*)&data); handle.eventHandler(out, (void*)&data);
} }
@ -1053,9 +1117,9 @@ static void manageUnitAttackEvent(color_ostream& out) {
data.attacker = unit1->id; data.attacker = unit1->id;
data.defender = unit2->id; data.defender = unit2->id;
data.wound = -1; data.wound = -1;
alreadyDone[data.attacker][data.defender] = 1;
for (auto &key_value : copy) { already_done.emplace(unit1->id, unit2->id);
EventHandler &handle = key_value.second; for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit2 killed unit attack event\n"); DEBUG(log,out).print("calling handler for unit2 killed unit attack event\n");
handle.eventHandler(out, (void*)&data); handle.eventHandler(out, (void*)&data);
} }
@ -1313,8 +1377,7 @@ static void manageInteractionEvent(color_ostream& out) {
lastAttacker = df::unit::find(data.attacker); lastAttacker = df::unit::find(data.attacker);
//lastDefender = df::unit::find(data.defender); //lastDefender = df::unit::find(data.defender);
//fire event //fire event
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for interaction event\n"); DEBUG(log,out).print("calling handler for interaction event\n");
handle.eventHandler(out, (void*)&data); 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(tiletypes tiletypes.cpp Brushes.h LINK_LIBRARIES lua)
#dfhack_plugin(title-folder title-folder.cpp) #dfhack_plugin(title-folder title-folder.cpp)
#dfhack_plugin(trackstop trackstop.cpp) #dfhack_plugin(trackstop trackstop.cpp)
#dfhack_plugin(tubefill tubefill.cpp) dfhack_plugin(tubefill tubefill.cpp)
#add_subdirectory(tweak) #add_subdirectory(tweak)
dfhack_plugin(workflow workflow.cpp LINK_LIBRARIES lua) dfhack_plugin(workflow workflow.cpp LINK_LIBRARIES lua)
dfhack_plugin(work-now work-now.cpp) dfhack_plugin(work-now work-now.cpp)

@ -62,12 +62,16 @@ local function do_export()
}:show() }:show()
end end
local function do_recheck()
dfhack.run_command('orders', 'recheck')
end
OrdersOverlay = defclass(OrdersOverlay, overlay.OverlayWidget) OrdersOverlay = defclass(OrdersOverlay, overlay.OverlayWidget)
OrdersOverlay.ATTRS{ OrdersOverlay.ATTRS{
default_pos={x=53,y=-6}, default_pos={x=53,y=-6},
default_enabled=true, default_enabled=true,
viewscreens='dwarfmode/Info/WORK_ORDERS/Default', viewscreens='dwarfmode/Info/WORK_ORDERS/Default',
frame={w=30, h=4}, frame={w=46, h=4},
} }
function OrdersOverlay:init() function OrdersOverlay:init()
@ -95,13 +99,20 @@ function OrdersOverlay:init()
}, },
widgets.HotkeyLabel{ widgets.HotkeyLabel{
frame={t=0, l=15}, 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', label='sort',
key='CUSTOM_CTRL_O', key='CUSTOM_CTRL_O',
auto_width=true, auto_width=true,
on_activate=do_sort, on_activate=do_sort,
}, },
widgets.HotkeyLabel{ widgets.HotkeyLabel{
frame={t=1, l=15}, frame={t=0, l=31},
label='clear', label='clear',
key='CUSTOM_CTRL_C', key='CUSTOM_CTRL_C',
auto_width=true, auto_width=true,
@ -157,7 +168,91 @@ function OrdersOverlay:render(dc)
OrdersOverlay.super.render(self, dc) OrdersOverlay.super.render(self, dc)
end 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 = { OVERLAY_WIDGETS = {
recheck=RecheckOverlay,
overlay=OrdersOverlay, overlay=OrdersOverlay,
} }

@ -10,6 +10,7 @@
#include "json/json.h" #include "json/json.h"
#include "df/building.h" #include "df/building.h"
#include "df/gamest.h"
#include "df/historical_figure.h" #include "df/historical_figure.h"
#include "df/itemdef_ammost.h" #include "df/itemdef_ammost.h"
#include "df/itemdef_armorst.h" #include "df/itemdef_armorst.h"
@ -36,6 +37,8 @@
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
using df::global::game;
DFHACK_PLUGIN("orders"); DFHACK_PLUGIN("orders");
REQUIRE_GLOBAL(world); 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_import_command(color_ostream & out, const std::string & name);
static command_result orders_clear_command(color_ostream & out); static command_result orders_clear_command(color_ostream & out);
static command_result orders_sort_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) 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); 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; return CR_WRONG_USAGE;
} }
@ -1015,3 +1033,27 @@ static command_result orders_sort_command(color_ostream & out)
return CR_OK; 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