commit
3c6ddc2a8c
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#ifndef EVENT_MANAGER_H_INCLUDED
|
||||
#define EVENT_MANAGER_H_INCLUDED
|
||||
|
||||
#include "Core.h"
|
||||
#include "Export.h"
|
||||
#include "ColorText.h"
|
||||
#include "PluginManager.h"
|
||||
#include "Console.h"
|
||||
|
||||
namespace DFHack {
|
||||
namespace EventManager {
|
||||
namespace EventType {
|
||||
enum EventType {
|
||||
TICK,
|
||||
JOB_INITIATED,
|
||||
JOB_COMPLETED,
|
||||
UNIT_DEATH,
|
||||
ITEM_CREATED,
|
||||
BUILDING,
|
||||
CONSTRUCTION,
|
||||
SYNDROME,
|
||||
INVASION,
|
||||
EVENT_MAX
|
||||
};
|
||||
}
|
||||
|
||||
struct EventHandler {
|
||||
void (*eventHandler)(color_ostream&, void*); //called when the event happens
|
||||
int32_t freq;
|
||||
|
||||
EventHandler(void (*eventHandlerIn)(color_ostream&, void*), int32_t freqIn): eventHandler(eventHandlerIn), freq(freqIn) {
|
||||
}
|
||||
|
||||
bool operator==(EventHandler& handle) const {
|
||||
return eventHandler == handle.eventHandler && freq == handle.freq;
|
||||
}
|
||||
bool operator!=(EventHandler& handle) const {
|
||||
return !( *this == handle);
|
||||
}
|
||||
};
|
||||
|
||||
struct SyndromeData {
|
||||
int32_t unitId;
|
||||
int32_t syndromeIndex;
|
||||
SyndromeData(int32_t unitId_in, int32_t syndromeIndex_in): unitId(unitId_in), syndromeIndex(syndromeIndex_in) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin);
|
||||
DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false);
|
||||
DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin);
|
||||
DFHACK_EXPORT void unregisterAll(Plugin* plugin);
|
||||
void manageEvents(color_ostream& out);
|
||||
void onStateChange(color_ostream& out, state_change_event event);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,503 @@
|
||||
#include "Core.h"
|
||||
#include "Console.h"
|
||||
#include "modules/Buildings.h"
|
||||
#include "modules/Constructions.h"
|
||||
#include "modules/EventManager.h"
|
||||
#include "modules/Job.h"
|
||||
#include "modules/World.h"
|
||||
|
||||
#include "df/building.h"
|
||||
#include "df/construction.h"
|
||||
#include "df/global_objects.h"
|
||||
#include "df/item.h"
|
||||
#include "df/job.h"
|
||||
#include "df/job_list_link.h"
|
||||
#include "df/ui.h"
|
||||
#include "df/unit.h"
|
||||
#include "df/unit_syndrome.h"
|
||||
#include "df/world.h"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace std;
|
||||
using namespace DFHack;
|
||||
using namespace EventManager;
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* error checking
|
||||
* consider a typedef instead of a struct for EventHandler
|
||||
**/
|
||||
|
||||
//map<uint32_t, vector<DFHack::EventManager::EventHandler> > tickQueue;
|
||||
multimap<uint32_t, EventHandler> tickQueue;
|
||||
|
||||
//TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever
|
||||
multimap<Plugin*, EventHandler> handlers[EventType::EVENT_MAX];
|
||||
uint32_t eventLastTick[EventType::EVENT_MAX];
|
||||
|
||||
const uint32_t ticksPerYear = 403200;
|
||||
|
||||
void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) {
|
||||
handlers[e].insert(pair<Plugin*, EventHandler>(plugin, handler));
|
||||
}
|
||||
|
||||
void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) {
|
||||
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
|
||||
+ DFHack::World::ReadCurrentTick();
|
||||
if ( !Core::getInstance().isWorldLoaded() ) {
|
||||
tick = 0;
|
||||
if ( absolute ) {
|
||||
Core::getInstance().getConsole().print("Warning: absolute flag will not be honored.\n");
|
||||
}
|
||||
}
|
||||
if ( absolute ) {
|
||||
tick = 0;
|
||||
}
|
||||
|
||||
tickQueue.insert(pair<uint32_t, EventHandler>(tick+(uint32_t)when, handler));
|
||||
handlers[EventType::TICK].insert(pair<Plugin*,EventHandler>(plugin,handler));
|
||||
return;
|
||||
}
|
||||
|
||||
void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) {
|
||||
for ( multimap<Plugin*, EventHandler>::iterator i = handlers[e].find(plugin); i != handlers[e].end(); i++ ) {
|
||||
if ( (*i).first != plugin )
|
||||
break;
|
||||
EventHandler handle = (*i).second;
|
||||
if ( handle == handler ) {
|
||||
handlers[e].erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void DFHack::EventManager::unregisterAll(Plugin* plugin) {
|
||||
for ( auto i = handlers[EventType::TICK].find(plugin); i != handlers[EventType::TICK].end(); i++ ) {
|
||||
if ( (*i).first != plugin )
|
||||
break;
|
||||
|
||||
//shenanigans to avoid concurrent modification
|
||||
EventHandler getRidOf = (*i).second;
|
||||
bool didSomething;
|
||||
do {
|
||||
didSomething = false;
|
||||
for ( auto j = tickQueue.begin(); j != tickQueue.end(); j++ ) {
|
||||
EventHandler candidate = (*j).second;
|
||||
if ( getRidOf != candidate )
|
||||
continue;
|
||||
tickQueue.erase(j);
|
||||
didSomething = true;
|
||||
break;
|
||||
}
|
||||
} while(didSomething);
|
||||
}
|
||||
for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) {
|
||||
handlers[a].erase(plugin);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static void manageTickEvent(color_ostream& out);
|
||||
static void manageJobInitiatedEvent(color_ostream& out);
|
||||
static void manageJobCompletedEvent(color_ostream& out);
|
||||
static void manageUnitDeathEvent(color_ostream& out);
|
||||
static void manageItemCreationEvent(color_ostream& out);
|
||||
static void manageBuildingEvent(color_ostream& out);
|
||||
static void manageConstructionEvent(color_ostream& out);
|
||||
static void manageSyndromeEvent(color_ostream& out);
|
||||
static void manageInvasionEvent(color_ostream& out);
|
||||
|
||||
//tick event
|
||||
static uint32_t lastTick = 0;
|
||||
|
||||
//job initiated
|
||||
static int32_t lastJobId = -1;
|
||||
|
||||
//job completed
|
||||
static unordered_map<int32_t, df::job*> prevJobs;
|
||||
|
||||
//unit death
|
||||
static unordered_set<int32_t> livingUnits;
|
||||
|
||||
//item creation
|
||||
static int32_t nextItem;
|
||||
|
||||
//building
|
||||
static int32_t nextBuilding;
|
||||
static unordered_set<int32_t> buildings;
|
||||
|
||||
//construction
|
||||
static unordered_set<df::construction*> constructions;
|
||||
static bool gameLoaded;
|
||||
|
||||
//invasion
|
||||
static int32_t nextInvasion;
|
||||
|
||||
void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) {
|
||||
static bool doOnce = false;
|
||||
if ( !doOnce ) {
|
||||
//TODO: put this somewhere else
|
||||
doOnce = true;
|
||||
EventHandler buildingHandler(Buildings::updateBuildings, 100);
|
||||
DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL);
|
||||
//out.print("Registered listeners.\n %d", __LINE__);
|
||||
}
|
||||
if ( event == DFHack::SC_WORLD_UNLOADED ) {
|
||||
lastTick = 0;
|
||||
lastJobId = -1;
|
||||
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
|
||||
Job::deleteJobStruct((*i).second);
|
||||
}
|
||||
prevJobs.clear();
|
||||
tickQueue.clear();
|
||||
livingUnits.clear();
|
||||
nextItem = -1;
|
||||
nextBuilding = -1;
|
||||
buildings.clear();
|
||||
constructions.clear();
|
||||
|
||||
Buildings::clearBuildings(out);
|
||||
gameLoaded = false;
|
||||
nextInvasion = -1;
|
||||
} else if ( event == DFHack::SC_WORLD_LOADED ) {
|
||||
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
|
||||
+ DFHack::World::ReadCurrentTick();
|
||||
multimap<uint32_t,EventHandler> newTickQueue;
|
||||
for ( auto i = tickQueue.begin(); i != tickQueue.end(); i++ ) {
|
||||
newTickQueue.insert(pair<uint32_t,EventHandler>(tick + (*i).first, (*i).second));
|
||||
}
|
||||
tickQueue.clear();
|
||||
|
||||
tickQueue.insert(newTickQueue.begin(), newTickQueue.end());
|
||||
|
||||
nextItem = 0;
|
||||
nextBuilding = 0;
|
||||
lastTick = 0;
|
||||
nextInvasion = df::global::ui->invasions.next_id;
|
||||
gameLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DFHack::EventManager::manageEvents(color_ostream& out) {
|
||||
if ( !gameLoaded ) {
|
||||
return;
|
||||
}
|
||||
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
|
||||
+ DFHack::World::ReadCurrentTick();
|
||||
|
||||
if ( tick <= lastTick )
|
||||
return;
|
||||
lastTick = tick;
|
||||
|
||||
int32_t eventFrequency[EventType::EVENT_MAX];
|
||||
for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) {
|
||||
int32_t min = 1000000000;
|
||||
for ( auto b = handlers[a].begin(); b != handlers[a].end(); b++ ) {
|
||||
EventHandler bob = (*b).second;
|
||||
if ( bob.freq < min )
|
||||
min = bob.freq;
|
||||
}
|
||||
eventFrequency[a] = min;
|
||||
}
|
||||
|
||||
manageTickEvent(out);
|
||||
if ( tick - eventLastTick[EventType::JOB_INITIATED] >= eventFrequency[EventType::JOB_INITIATED] ) {
|
||||
manageJobInitiatedEvent(out);
|
||||
eventLastTick[EventType::JOB_INITIATED] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= eventFrequency[EventType::JOB_COMPLETED] ) {
|
||||
manageJobCompletedEvent(out);
|
||||
eventLastTick[EventType::JOB_COMPLETED] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::UNIT_DEATH] >= eventFrequency[EventType::UNIT_DEATH] ) {
|
||||
manageUnitDeathEvent(out);
|
||||
eventLastTick[EventType::UNIT_DEATH] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::ITEM_CREATED] >= eventFrequency[EventType::ITEM_CREATED] ) {
|
||||
manageItemCreationEvent(out);
|
||||
eventLastTick[EventType::ITEM_CREATED] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::BUILDING] >= eventFrequency[EventType::BUILDING] ) {
|
||||
manageBuildingEvent(out);
|
||||
eventLastTick[EventType::BUILDING] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::CONSTRUCTION] >= eventFrequency[EventType::CONSTRUCTION] ) {
|
||||
manageConstructionEvent(out);
|
||||
eventLastTick[EventType::CONSTRUCTION] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::SYNDROME] >= eventFrequency[EventType::SYNDROME] ) {
|
||||
manageSyndromeEvent(out);
|
||||
eventLastTick[EventType::SYNDROME] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::INVASION] >= eventFrequency[EventType::INVASION] ) {
|
||||
manageInvasionEvent(out);
|
||||
eventLastTick[EventType::INVASION] = tick;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void manageTickEvent(color_ostream& out) {
|
||||
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
|
||||
+ DFHack::World::ReadCurrentTick();
|
||||
while ( !tickQueue.empty() ) {
|
||||
if ( tick < (*tickQueue.begin()).first )
|
||||
break;
|
||||
EventHandler handle = (*tickQueue.begin()).second;
|
||||
tickQueue.erase(tickQueue.begin());
|
||||
handle.eventHandler(out, (void*)tick);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void manageJobInitiatedEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::JOB_INITIATED].empty() )
|
||||
return;
|
||||
|
||||
if ( lastJobId == -1 ) {
|
||||
lastJobId = *df::global::job_next_id - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( lastJobId+1 == *df::global::job_next_id ) {
|
||||
return; //no new jobs
|
||||
}
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_INITIATED].begin(), handlers[EventType::JOB_INITIATED].end());
|
||||
|
||||
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
|
||||
if ( link->item == NULL )
|
||||
continue;
|
||||
if ( link->item->id <= lastJobId )
|
||||
continue;
|
||||
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
|
||||
(*i).second.eventHandler(out, (void*)link->item);
|
||||
}
|
||||
}
|
||||
|
||||
lastJobId = *df::global::job_next_id - 1;
|
||||
}
|
||||
|
||||
static void manageJobCompletedEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::JOB_COMPLETED].empty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end());
|
||||
map<int32_t, df::job*> nowJobs;
|
||||
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
|
||||
if ( link->item == NULL )
|
||||
continue;
|
||||
nowJobs[link->item->id] = link->item;
|
||||
}
|
||||
|
||||
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
|
||||
if ( nowJobs.find((*i).first) != nowJobs.end() )
|
||||
continue;
|
||||
|
||||
//recently finished or cancelled job!
|
||||
for ( auto j = copy.begin(); j != copy.end(); j++ ) {
|
||||
(*j).second.eventHandler(out, (void*)(*i).second);
|
||||
}
|
||||
}
|
||||
|
||||
//erase old jobs, copy over possibly altered jobs
|
||||
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
|
||||
Job::deleteJobStruct((*i).second);
|
||||
}
|
||||
prevJobs.clear();
|
||||
|
||||
//create new jobs
|
||||
for ( auto j = nowJobs.begin(); j != nowJobs.end(); j++ ) {
|
||||
/*map<int32_t, df::job*>::iterator i = prevJobs.find((*j).first);
|
||||
if ( i != prevJobs.end() ) {
|
||||
continue;
|
||||
}*/
|
||||
|
||||
df::job* newJob = Job::cloneJobStruct((*j).second, true);
|
||||
prevJobs[newJob->id] = newJob;
|
||||
}
|
||||
|
||||
/*//get rid of old pointers to deallocated jobs
|
||||
for ( size_t a = 0; a < toDelete.size(); a++ ) {
|
||||
prevJobs.erase(a);
|
||||
}*/
|
||||
}
|
||||
|
||||
static void manageUnitDeathEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::UNIT_DEATH].empty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end());
|
||||
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
|
||||
df::unit* unit = df::global::world->units.active[a];
|
||||
if ( unit->counters.death_id == -1 ) {
|
||||
livingUnits.insert(unit->id);
|
||||
continue;
|
||||
}
|
||||
//dead: if dead since last check, trigger events
|
||||
if ( livingUnits.find(unit->id) == livingUnits.end() )
|
||||
continue;
|
||||
|
||||
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
|
||||
(*i).second.eventHandler(out, (void*)unit->id);
|
||||
}
|
||||
livingUnits.erase(unit->id);
|
||||
}
|
||||
}
|
||||
|
||||
static void manageItemCreationEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::ITEM_CREATED].empty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( nextItem >= *df::global::item_next_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) {
|
||||
df::item* item = df::global::world->items.all[a];
|
||||
//invaders
|
||||
if ( item->flags.bits.foreign )
|
||||
continue;
|
||||
//traders who bring back your items?
|
||||
if ( item->flags.bits.trader )
|
||||
continue;
|
||||
//migrants
|
||||
if ( item->flags.bits.owned )
|
||||
continue;
|
||||
//spider webs don't count
|
||||
if ( item->flags.bits.spider_web )
|
||||
continue;
|
||||
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
|
||||
(*i).second.eventHandler(out, (void*)item->id);
|
||||
}
|
||||
}
|
||||
nextItem = *df::global::item_next_id;
|
||||
}
|
||||
|
||||
static void manageBuildingEvent(color_ostream& out) {
|
||||
/*
|
||||
* TODO: could be faster
|
||||
* consider looking at jobs: building creation / destruction
|
||||
**/
|
||||
if ( handlers[EventType::BUILDING].empty() )
|
||||
return;
|
||||
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end());
|
||||
//first alert people about 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 ) {
|
||||
//out.print("%s, line %d: Couldn't find new building with id %d.\n", __FILE__, __LINE__, a);
|
||||
//the tricky thing is that when the game first starts, it's ok to skip buildings, but otherwise, if you skip buildings, something is probably wrong. TODO: make this smarter
|
||||
continue;
|
||||
}
|
||||
buildings.insert(a);
|
||||
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
|
||||
EventHandler bob = (*b).second;
|
||||
bob.eventHandler(out, (void*)a);
|
||||
}
|
||||
}
|
||||
nextBuilding = *df::global::building_next_id;
|
||||
|
||||
//now alert people about destroyed buildings
|
||||
unordered_set<int32_t> toDelete;
|
||||
for ( auto a = buildings.begin(); a != buildings.end(); a++ ) {
|
||||
int32_t id = *a;
|
||||
int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id);
|
||||
if ( index != -1 )
|
||||
continue;
|
||||
toDelete.insert(id);
|
||||
|
||||
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
|
||||
EventHandler bob = (*b).second;
|
||||
bob.eventHandler(out, (void*)id);
|
||||
}
|
||||
}
|
||||
|
||||
for ( auto a = toDelete.begin(); a != toDelete.end(); a++ ) {
|
||||
int32_t id = *a;
|
||||
buildings.erase(id);
|
||||
}
|
||||
|
||||
//out.print("Sent building event.\n %d", __LINE__);
|
||||
}
|
||||
|
||||
static void manageConstructionEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::CONSTRUCTION].empty() )
|
||||
return;
|
||||
|
||||
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());
|
||||
for ( auto a = constructions.begin(); a != constructions.end(); a++ ) {
|
||||
df::construction* construction = *a;
|
||||
if ( constructionsNow.find(construction) != constructionsNow.end() )
|
||||
continue;
|
||||
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
|
||||
EventHandler handle = (*b).second;
|
||||
handle.eventHandler(out, (void*)construction);
|
||||
}
|
||||
}
|
||||
|
||||
for ( auto a = constructionsNow.begin(); a != constructionsNow.end(); a++ ) {
|
||||
df::construction* construction = *a;
|
||||
if ( constructions.find(construction) != constructions.end() )
|
||||
continue;
|
||||
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
|
||||
EventHandler handle = (*b).second;
|
||||
handle.eventHandler(out, (void*)construction);
|
||||
}
|
||||
}
|
||||
|
||||
constructions.clear();
|
||||
constructions.insert(constructionsNow.begin(), constructionsNow.end());
|
||||
}
|
||||
|
||||
static void manageSyndromeEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::SYNDROME].empty() )
|
||||
return;
|
||||
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end());
|
||||
for ( auto a = df::global::world->units.active.begin(); a != df::global::world->units.active.end(); a++ ) {
|
||||
df::unit* unit = *a;
|
||||
if ( unit->flags1.bits.dead )
|
||||
continue;
|
||||
for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) {
|
||||
df::unit_syndrome* syndrome = unit->syndromes.active[b];
|
||||
uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time;
|
||||
if ( startTime <= eventLastTick[EventType::SYNDROME] )
|
||||
continue;
|
||||
|
||||
SyndromeData data(unit->id, b);
|
||||
for ( auto c = copy.begin(); c != copy.end(); c++ ) {
|
||||
EventHandler handle = (*c).second;
|
||||
handle.eventHandler(out, (void*)&data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void manageInvasionEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::INVASION].empty() )
|
||||
return;
|
||||
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::INVASION].begin(), handlers[EventType::INVASION].end());
|
||||
|
||||
if ( df::global::ui->invasions.next_id <= nextInvasion )
|
||||
return;
|
||||
nextInvasion = df::global::ui->invasions.next_id;
|
||||
|
||||
for ( auto a = copy.begin(); a != copy.end(); a++ ) {
|
||||
EventHandler handle = (*a).second;
|
||||
handle.eventHandler(out, (void*)nextInvasion);
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 42e26b368f48a148aba07fea295c6d19bca3fcbc
|
||||
Subproject commit 55cb628224f9da7d39e88e62a312d877aeed537f
|
@ -0,0 +1,477 @@
|
||||
#include "PluginManager.h"
|
||||
#include "Export.h"
|
||||
#include "DataDefs.h"
|
||||
#include "Core.h"
|
||||
|
||||
#include "modules/EventManager.h"
|
||||
#include "modules/Job.h"
|
||||
#include "modules/Maps.h"
|
||||
|
||||
#include "df/building.h"
|
||||
#include "df/caste_raw.h"
|
||||
#include "df/creature_raw.h"
|
||||
#include "df/global_objects.h"
|
||||
#include "df/item.h"
|
||||
#include "df/item_boulderst.h"
|
||||
#include "df/job.h"
|
||||
#include "df/job_type.h"
|
||||
#include "df/reaction.h"
|
||||
#include "df/reaction_product.h"
|
||||
#include "df/reaction_product_type.h"
|
||||
#include "df/reaction_product_itemst.h"
|
||||
#include "df/syndrome.h"
|
||||
#include "df/unit_syndrome.h"
|
||||
#include "df/ui.h"
|
||||
#include "df/unit.h"
|
||||
#include "df/general_ref.h"
|
||||
#include "df/general_ref_building_holderst.h"
|
||||
#include "df/general_ref_type.h"
|
||||
#include "df/general_ref_unit_workerst.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace std;
|
||||
using namespace DFHack;
|
||||
|
||||
/*
|
||||
Example usage:
|
||||
|
||||
//////////////////////////////////////////////
|
||||
//In file inorganic_duck.txt
|
||||
inorganic_stone_duck
|
||||
|
||||
[OBJECT:INORGANIC]
|
||||
|
||||
[INORGANIC:DUCK_ROCK]
|
||||
[USE_MATERIAL_TEMPLATE:STONE_TEMPLATE]
|
||||
[STATE_NAME_ADJ:ALL_SOLID:drakium][DISPLAY_COLOR:0:7:0][TILE:'.']
|
||||
[IS_STONE]
|
||||
[SOLID_DENSITY:1][MELTING_POINT:25000]
|
||||
[BOILING_POINT:9999] //This is the critical line: boiling point must be <= 10000
|
||||
[SYNDROME]
|
||||
[SYN_NAME:Chronic Duck Syndrome]
|
||||
[CE_BODY_TRANSFORMATION:PROB:100:START:0]
|
||||
[CE:CREATURE:BIRD_DUCK:MALE] //even though we don't have SYN_INHALED, the plugin will add it
|
||||
///////////////////////////////////////////////
|
||||
//In file building_duck.txt
|
||||
building_duck
|
||||
|
||||
[OBJECT:BUILDING]
|
||||
|
||||
[BUILDING_WORKSHOP:DUCK_WORKSHOP]
|
||||
[NAME:Duck Workshop]
|
||||
[NAME_COLOR:7:0:1]
|
||||
[DIM:1:1]
|
||||
[WORK_LOCATION:1:1]
|
||||
[BLOCK:1:0:0:0]
|
||||
[TILE:0:1:236]
|
||||
[COLOR:0:1:0:0:1]
|
||||
[TILE:1:1:' ']
|
||||
[COLOR:1:1:0:0:0]
|
||||
[TILE:2:1:8]
|
||||
[COLOR:2:1:0:0:1]
|
||||
[TILE:3:1:8]
|
||||
[COLOR:3:2:0:4:1]
|
||||
[BUILD_ITEM:1:NONE:NONE:NONE:NONE]
|
||||
[BUILDMAT]
|
||||
[WORTHLESS_STONE_ONLY]
|
||||
[CAN_USE_ARTIFACT]
|
||||
///////////////////////////////////////////////
|
||||
//In file reaction_duck.txt
|
||||
reaction_duck
|
||||
|
||||
[OBJECT:REACTION]
|
||||
|
||||
[REACTION:DUCKIFICATION]
|
||||
[NAME:become a duck]
|
||||
[BUILDING:DUCK_WORKSHOP:NONE]
|
||||
[PRODUCT:100:100:STONE:NO_SUBTYPE:STONE:DUCK_ROCK]
|
||||
//////////////////////////////////////////////
|
||||
//Add the following lines to your entity in entity_default.txt (or wherever it is)
|
||||
[PERMITTED_BUILDING:DUCK_WORKSHOP]
|
||||
[PERMITTED_REACTION:DUCKIFICATION]
|
||||
//////////////////////////////////////////////
|
||||
|
||||
Next, start a new fort in a new world, build a duck workshop, then have someone become a duck.
|
||||
*/
|
||||
|
||||
bool enabled = true;
|
||||
|
||||
DFHACK_PLUGIN("autoSyndrome");
|
||||
|
||||
command_result autoSyndrome(color_ostream& out, vector<string>& parameters);
|
||||
void processJob(color_ostream& out, void* jobPtr);
|
||||
int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome);
|
||||
|
||||
DFhackCExport command_result plugin_init(color_ostream& out, vector<PluginCommand> &commands) {
|
||||
commands.push_back(PluginCommand("autoSyndrome", "Automatically give units syndromes when they complete jobs, as configured in the raw files.\n", &autoSyndrome, false,
|
||||
"autoSyndrome:\n"
|
||||
" autoSyndrome 0 //disable\n"
|
||||
" autoSyndrome 1 //enable\n"
|
||||
" autoSyndrome disable //disable\n"
|
||||
" autoSyndrome enable //enable\n"
|
||||
"\n"
|
||||
"autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the dwarf that finished that job the syndrome specified in the raw files.\n"
|
||||
"\n"
|
||||
"Requirements:\n"
|
||||
" 1) The job must be a custom reaction.\n"
|
||||
" 2) The job must produce a stone of some inorganic material.\n"
|
||||
" 3) The stone must have a boiling temperature less than or equal to 9000.\n"
|
||||
"\n"
|
||||
"When these conditions are met, the unit that completed the job will immediately become afflicted with all applicable syndromes associated with the inorganic material of the stone, or stones. It should correctly check for whether the creature or caste is affected or immune, and it should also correctly account for affected and immune creature classes.\n"
|
||||
"Multiple syndromes per stone, or multiple boiling rocks produced with the same reaction should work fine.\n"
|
||||
));
|
||||
|
||||
|
||||
Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome");
|
||||
EventManager::EventHandler handle(processJob, 5);
|
||||
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me);
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown(color_ostream& out) {
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
/*DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event e) {
|
||||
return CR_OK;
|
||||
}*/
|
||||
|
||||
command_result autoSyndrome(color_ostream& out, vector<string>& parameters) {
|
||||
if ( parameters.size() > 1 )
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
bool wasEnabled = enabled;
|
||||
if ( parameters.size() == 1 ) {
|
||||
if ( parameters[0] == "enable" ) {
|
||||
enabled = true;
|
||||
} else if ( parameters[0] == "disable" ) {
|
||||
enabled = false;
|
||||
} else {
|
||||
int32_t a = atoi(parameters[0].c_str());
|
||||
if ( a < 0 || a > 1 )
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
enabled = (bool)a;
|
||||
}
|
||||
}
|
||||
|
||||
out.print("autoSyndrome is %s\n", enabled ? "enabled" : "disabled");
|
||||
if ( enabled == wasEnabled )
|
||||
return CR_OK;
|
||||
|
||||
Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome");
|
||||
if ( enabled ) {
|
||||
EventManager::EventHandler handle(processJob, 5);
|
||||
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me);
|
||||
} else {
|
||||
EventManager::unregisterAll(me);
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df::unit* unit) {
|
||||
df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race];
|
||||
df::caste_raw* caste = creature->caste[unit->caste];
|
||||
std::string& creature_name = creature->creature_id;
|
||||
std::string& creature_caste = caste->caste_id;
|
||||
//check that the syndrome applies to that guy
|
||||
/*
|
||||
* If there is no affected class or affected creature, then anybody who isn't immune is fair game.
|
||||
*
|
||||
* Otherwise, it works like this:
|
||||
* add all the affected class creatures
|
||||
* add all the affected creatures
|
||||
* remove all the immune class creatures
|
||||
* remove all the immune creatures
|
||||
* you're affected if and only if you're in the remaining list after all of that
|
||||
**/
|
||||
bool applies = syndrome->syn_affected_class.size() == 0 && syndrome->syn_affected_creature.size() == 0;
|
||||
for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) {
|
||||
if ( applies )
|
||||
break;
|
||||
for ( size_t d = 0; d < caste->creature_class.size(); d++ ) {
|
||||
if ( *syndrome->syn_affected_class[c] == *caste->creature_class[d] ) {
|
||||
applies = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) {
|
||||
if ( !applies )
|
||||
break;
|
||||
for ( size_t d = 0; d < caste->creature_class.size(); d++ ) {
|
||||
if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) {
|
||||
applies = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( syndrome->syn_affected_creature.size() != syndrome->syn_affected_caste.size() ) {
|
||||
out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__);
|
||||
return false;
|
||||
}
|
||||
for ( size_t c = 0; c < syndrome->syn_affected_creature.size(); c++ ) {
|
||||
if ( creature_name != *syndrome->syn_affected_creature[c] )
|
||||
continue;
|
||||
if ( *syndrome->syn_affected_caste[c] == "ALL" ||
|
||||
*syndrome->syn_affected_caste[c] == creature_caste ) {
|
||||
applies = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for ( size_t c = 0; c < syndrome->syn_immune_creature.size(); c++ ) {
|
||||
if ( creature_name != *syndrome->syn_immune_creature[c] )
|
||||
continue;
|
||||
if ( *syndrome->syn_immune_caste[c] == "ALL" ||
|
||||
*syndrome->syn_immune_caste[c] == creature_caste ) {
|
||||
applies = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ( !applies ) {
|
||||
return false;
|
||||
}
|
||||
if ( giveSyndrome(out, workerId, syndrome) < 0 )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void processJob(color_ostream& out, void* jobPtr) {
|
||||
df::job* job = (df::job*)jobPtr;
|
||||
if ( job == NULL ) {
|
||||
out.print("Error %s line %d: null job.\n", __FILE__, __LINE__);
|
||||
return;
|
||||
}
|
||||
if ( job->completion_timer > 0 )
|
||||
return;
|
||||
|
||||
if ( job->job_type != df::job_type::CustomReaction )
|
||||
return;
|
||||
|
||||
df::reaction* reaction = NULL;
|
||||
for ( size_t a = 0; a < df::global::world->raws.reactions.size(); a++ ) {
|
||||
df::reaction* candidate = df::global::world->raws.reactions[a];
|
||||
if ( candidate->code != job->reaction_name )
|
||||
continue;
|
||||
reaction = candidate;
|
||||
break;
|
||||
}
|
||||
if ( reaction == NULL ) {
|
||||
out.print("%s, line %d: could not find reaction \"%s\".\n", __FILE__, __LINE__, job->reaction_name.c_str() );
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t workerId = -1;
|
||||
for ( size_t a = 0; a < job->general_refs.size(); a++ ) {
|
||||
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER )
|
||||
continue;
|
||||
if ( workerId != -1 ) {
|
||||
out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__);
|
||||
}
|
||||
workerId = ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id;
|
||||
if (workerId == -1) {
|
||||
out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t workerIndex = df::unit::binsearch_index(df::global::world->units.all, workerId);
|
||||
if ( workerIndex < 0 ) {
|
||||
out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId);
|
||||
return;
|
||||
}
|
||||
df::unit* worker = df::global::world->units.all[workerIndex];
|
||||
//find the building that made it
|
||||
int32_t buildingId = -1;
|
||||
for ( size_t a = 0; a < job->general_refs.size(); a++ ) {
|
||||
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::BUILDING_HOLDER )
|
||||
continue;
|
||||
if ( buildingId != -1 ) {
|
||||
out.print("%s, line %d: Found two buildings for the same job.\n", __FILE__, __LINE__);
|
||||
}
|
||||
buildingId = ((df::general_ref_building_holderst*)job->general_refs[a])->building_id;
|
||||
if (buildingId == -1) {
|
||||
out.print("%s, line %d: invalid building.\n", __FILE__, __LINE__);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
df::building* building;
|
||||
{
|
||||
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, buildingId);
|
||||
if ( index == -1 ) {
|
||||
out.print("%s, line %d: error: couldn't find building %d.\n", __FILE__, __LINE__, buildingId);
|
||||
return;
|
||||
}
|
||||
building = df::global::world->buildings.all[index];
|
||||
}
|
||||
|
||||
//find all of the products it makes. Look for a stone with a low boiling point.
|
||||
bool appliedSomething = false;
|
||||
for ( size_t a = 0; a < reaction->products.size(); a++ ) {
|
||||
df::reaction_product_type type = reaction->products[a]->getType();
|
||||
//out.print("type = %d\n", (int32_t)type);
|
||||
if ( type != df::enums::reaction_product_type::item )
|
||||
continue;
|
||||
df::reaction_product_itemst* bob = (df::reaction_product_itemst*)reaction->products[a];
|
||||
//out.print("item_type = %d\n", (int32_t)bob->item_type);
|
||||
if ( bob->item_type != df::enums::item_type::BOULDER )
|
||||
continue;
|
||||
//for now don't worry about subtype
|
||||
|
||||
//must be a boiling rock syndrome
|
||||
df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index];
|
||||
if ( inorganic->material.heat.boiling_point > 9000 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ( size_t b = 0; b < inorganic->material.syndrome.size(); b++ ) {
|
||||
//add each syndrome to the guy who did the job
|
||||
df::syndrome* syndrome = inorganic->material.syndrome[b];
|
||||
bool workerOnly = false;
|
||||
bool allowMultipleTargets = false;
|
||||
bool foundCommand = false;
|
||||
bool destroyRock = true;
|
||||
string commandStr;
|
||||
vector<string> args;
|
||||
for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) {
|
||||
std::string* clazz = syndrome->syn_class[c];
|
||||
if ( foundCommand ) {
|
||||
if ( commandStr == "" ) {
|
||||
if ( *clazz == "\\WORKER_ONLY" ) {
|
||||
workerOnly = true;
|
||||
} else if ( *clazz == "\\ALLOW_MULTIPLE_TARGETS" ) {
|
||||
allowMultipleTargets = true;
|
||||
} else if ( *clazz == "\\PRESERVE_ROCK" ) {
|
||||
destroyRock = false;
|
||||
}
|
||||
else {
|
||||
commandStr = *clazz;
|
||||
}
|
||||
} else {
|
||||
stringstream bob;
|
||||
if ( *clazz == "\\LOCATION" ) {
|
||||
bob << job->pos.x;
|
||||
args.push_back(bob.str());
|
||||
bob.str("");
|
||||
bob.clear();
|
||||
|
||||
bob << job->pos.y;
|
||||
args.push_back(bob.str());
|
||||
bob.str("");
|
||||
bob.clear();
|
||||
|
||||
bob << job->pos.z;
|
||||
args.push_back(bob.str());
|
||||
bob.str("");
|
||||
bob.clear();
|
||||
} else if ( *clazz == "\\WORKER_ID" ) {
|
||||
bob << workerId;
|
||||
args.push_back(bob.str());
|
||||
} else if ( *clazz == "\\REACTION_INDEX" ) {
|
||||
bob << reaction->index;
|
||||
args.push_back(bob.str());
|
||||
} else {
|
||||
args.push_back(*clazz);
|
||||
}
|
||||
}
|
||||
} else if ( *clazz == "\\COMMAND" ) {
|
||||
foundCommand = true;
|
||||
}
|
||||
}
|
||||
if ( commandStr != "" ) {
|
||||
Core::getInstance().runCommand(out, commandStr, args);
|
||||
}
|
||||
|
||||
if ( destroyRock ) {
|
||||
//find the rock and kill it before it can boil and cause problems and ugliness
|
||||
for ( size_t c = 0; c < df::global::world->items.all.size(); c++ ) {
|
||||
df::item* item = df::global::world->items.all[c];
|
||||
if ( item->pos.z != building->z )
|
||||
continue;
|
||||
if ( item->pos.x < building->x1 || item->pos.x > building->x2 )
|
||||
continue;
|
||||
if ( item->pos.y < building->y1 || item->pos.y > building->y2 )
|
||||
continue;
|
||||
if ( item->getType() != df::enums::item_type::BOULDER )
|
||||
continue;
|
||||
//make sure it's the right type of boulder
|
||||
df::item_boulderst* boulder = (df::item_boulderst*)item;
|
||||
if ( boulder->mat_index != bob->mat_index )
|
||||
continue;
|
||||
|
||||
boulder->flags.bits.garbage_collect = true;
|
||||
boulder->flags.bits.forbid = true;
|
||||
boulder->flags.bits.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
//only one syndrome per reaction will be applied, unless multiples are allowed.
|
||||
if ( appliedSomething && !allowMultipleTargets )
|
||||
continue;
|
||||
|
||||
if ( maybeApply(out, syndrome, workerId, worker) ) {
|
||||
appliedSomething = true;
|
||||
}
|
||||
|
||||
if ( workerOnly )
|
||||
continue;
|
||||
|
||||
//now try applying it to everybody inside the building
|
||||
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
|
||||
df::unit* unit = df::global::world->units.active[a];
|
||||
if ( unit == worker )
|
||||
continue;
|
||||
if ( unit->pos.z != building->z )
|
||||
continue;
|
||||
if ( unit->pos.x < building->x1 || unit->pos.x > building->x2 )
|
||||
continue;
|
||||
if ( unit->pos.y < building->y1 || unit->pos.y > building->y2 )
|
||||
continue;
|
||||
if ( maybeApply(out, syndrome, unit->id, unit) ) {
|
||||
appliedSomething = true;
|
||||
if ( !allowMultipleTargets )
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Heavily based on https://gist.github.com/4061959/
|
||||
**/
|
||||
int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome) {
|
||||
int32_t index = df::unit::binsearch_index(df::global::world->units.all, workerId);
|
||||
if ( index < 0 ) {
|
||||
out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId);
|
||||
return -1;
|
||||
}
|
||||
df::unit* unit = df::global::world->units.all[index];
|
||||
|
||||
df::unit_syndrome* unitSyndrome = new df::unit_syndrome();
|
||||
unitSyndrome->type = syndrome->id;
|
||||
unitSyndrome->year = 0;
|
||||
unitSyndrome->year_time = 0;
|
||||
unitSyndrome->ticks = 1;
|
||||
unitSyndrome->unk1 = 1;
|
||||
unitSyndrome->flags = 0; //typecast
|
||||
|
||||
for ( size_t a = 0; a < syndrome->ce.size(); a++ ) {
|
||||
df::unit_syndrome::T_symptoms* symptom = new df::unit_syndrome::T_symptoms();
|
||||
symptom->unk1 = 0;
|
||||
symptom->unk2 = 0;
|
||||
symptom->ticks = 1;
|
||||
symptom->flags = 2; //TODO: ???
|
||||
unitSyndrome->symptoms.push_back(symptom);
|
||||
}
|
||||
unit->syndromes.active.push_back(unitSyndrome);
|
||||
return 0;
|
||||
}
|
||||
|
@ -0,0 +1,108 @@
|
||||
|
||||
#include "Console.h"
|
||||
#include "Core.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
#include "modules/EventManager.h"
|
||||
#include "DataDefs.h"
|
||||
|
||||
#include "df/item.h"
|
||||
#include "df/world.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace std;
|
||||
|
||||
DFHACK_PLUGIN("eventExample");
|
||||
|
||||
void jobInitiated(color_ostream& out, void* job);
|
||||
void jobCompleted(color_ostream& out, void* job);
|
||||
void timePassed(color_ostream& out, void* ptr);
|
||||
void unitDeath(color_ostream& out, void* ptr);
|
||||
void itemCreate(color_ostream& out, void* ptr);
|
||||
void building(color_ostream& out, void* ptr);
|
||||
void construction(color_ostream& out, void* ptr);
|
||||
void syndrome(color_ostream& out, void* ptr);
|
||||
void invasion(color_ostream& out, void* ptr);
|
||||
|
||||
command_result eventExample(color_ostream& out, vector<string>& parameters);
|
||||
|
||||
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) {
|
||||
commands.push_back(PluginCommand("eventExample", "Sets up a few event triggers.",eventExample));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
command_result eventExample(color_ostream& out, vector<string>& parameters) {
|
||||
EventManager::EventHandler initiateHandler(jobInitiated, 10);
|
||||
EventManager::EventHandler completeHandler(jobCompleted, 5);
|
||||
EventManager::EventHandler timeHandler(timePassed, 1);
|
||||
EventManager::EventHandler deathHandler(unitDeath, 500);
|
||||
EventManager::EventHandler itemHandler(itemCreate, 1000);
|
||||
EventManager::EventHandler buildingHandler(building, 500);
|
||||
EventManager::EventHandler constructionHandler(construction, 100);
|
||||
EventManager::EventHandler syndromeHandler(syndrome, 1);
|
||||
EventManager::EventHandler invasionHandler(invasion, 1000);
|
||||
Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample");
|
||||
EventManager::unregisterAll(me);
|
||||
|
||||
EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me);
|
||||
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me);
|
||||
EventManager::registerTick(timeHandler, 1, me);
|
||||
EventManager::registerTick(timeHandler, 2, me);
|
||||
EventManager::registerTick(timeHandler, 4, me);
|
||||
EventManager::registerTick(timeHandler, 8, me);
|
||||
EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, me);
|
||||
EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, me);
|
||||
EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, me);
|
||||
EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, me);
|
||||
EventManager::registerListener(EventManager::EventType::SYNDROME, syndromeHandler, me);
|
||||
EventManager::registerListener(EventManager::EventType::INVASION, invasionHandler, me);
|
||||
out.print("Events registered.\n");
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
void jobInitiated(color_ostream& out, void* job) {
|
||||
out.print("Job initiated! 0x%X\n", job);
|
||||
}
|
||||
|
||||
void jobCompleted(color_ostream& out, void* job) {
|
||||
out.print("Job completed! 0x%X\n", job);
|
||||
}
|
||||
|
||||
void timePassed(color_ostream& out, void* ptr) {
|
||||
out.print("Time: %d\n", (int32_t)(ptr));
|
||||
}
|
||||
|
||||
void unitDeath(color_ostream& out, void* ptr) {
|
||||
out.print("Death: %d\n", (int32_t)(ptr));
|
||||
}
|
||||
|
||||
void itemCreate(color_ostream& out, void* ptr) {
|
||||
int32_t item_index = df::item::binsearch_index(df::global::world->items.all, (int32_t)ptr);
|
||||
if ( item_index == -1 ) {
|
||||
out.print("%s, %d: Error.\n", __FILE__, __LINE__);
|
||||
}
|
||||
df::item* item = df::global::world->items.all[item_index];
|
||||
df::item_type type = item->getType();
|
||||
df::coord pos = item->pos;
|
||||
out.print("Item created: %d, %s, at (%d,%d,%d)\n", (int32_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
void building(color_ostream& out, void* ptr) {
|
||||
out.print("Building created/destroyed: %d\n", (int32_t)ptr);
|
||||
}
|
||||
|
||||
void construction(color_ostream& out, void* ptr) {
|
||||
out.print("Construction created/destroyed: 0x%X\n", ptr);
|
||||
}
|
||||
|
||||
void syndrome(color_ostream& out, void* ptr) {
|
||||
EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr;
|
||||
out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex);
|
||||
}
|
||||
|
||||
void invasion(color_ostream& out, void* ptr) {
|
||||
out.print("New invasion! %d\n", (int32_t)ptr);
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
|
||||
#include "Console.h"
|
||||
#include "Core.h"
|
||||
#include "DataDefs.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace std;
|
||||
|
||||
command_result printArgs (color_ostream &out, std::vector <std::string> & parameters);
|
||||
|
||||
DFHACK_PLUGIN("printArgs");
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
commands.push_back(PluginCommand(
|
||||
"printArgs", "Print the arguments given.",
|
||||
printArgs, false
|
||||
));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
command_result printArgs (color_ostream &out, std::vector <std::string> & parameters)
|
||||
{
|
||||
for ( size_t a = 0; a < parameters.size(); a++ ) {
|
||||
out << "Argument " << (a+1) << ": \"" << parameters[a] << "\"" << endl;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
|
||||
// DF data structure definition headers
|
||||
#include "DataDefs.h"
|
||||
#include "df/world.h"
|
||||
|
||||
#include "modules/Gui.h"
|
||||
#include "modules/Maps.h"
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
|
||||
|
||||
command_result stepBetween (color_ostream &out, std::vector <std::string> & parameters);
|
||||
|
||||
// A plugin must be able to return its name and version.
|
||||
// The name string provided must correspond to the filename - skeleton.plug.so or skeleton.plug.dll in this case
|
||||
DFHACK_PLUGIN("stepBetween");
|
||||
|
||||
// Mandatory init function. If you have some global state, create it here.
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
// Fill the command list with your commands.
|
||||
commands.push_back(PluginCommand(
|
||||
"stepBetween", "Do nothing, look pretty.",
|
||||
stepBetween, false, /* true means that the command can't be used from non-interactive user interface */
|
||||
// Extended help string. Used by CR_WRONG_USAGE and the help command:
|
||||
" This command does nothing at all.\n"
|
||||
));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
// This is called right before the plugin library is removed from memory.
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
// You *MUST* kill all threads you created before this returns.
|
||||
// If everything fails, just return CR_FAILURE. Your plugin will be
|
||||
// in a zombie state, but things won't crash.
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
// Called to notify the plugin about important state changes.
|
||||
// Invoked with DF suspended, and always before the matching plugin_onupdate.
|
||||
// More event codes may be added in the future.
|
||||
/*
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case SC_GAME_LOADED:
|
||||
// initialize from the world just loaded
|
||||
break;
|
||||
case SC_GAME_UNLOADED:
|
||||
// cleanup
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
*/
|
||||
|
||||
// Whatever you put here will be done in each game step. Don't abuse it.
|
||||
// It's optional, so you can just comment it out like this if you don't need it.
|
||||
/*
|
||||
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
|
||||
{
|
||||
// whetever. You don't need to suspend DF execution here.
|
||||
return CR_OK;
|
||||
}
|
||||
*/
|
||||
|
||||
df::coord prev;
|
||||
|
||||
// A command! It sits around and looks pretty. And it's nice and friendly.
|
||||
command_result stepBetween (color_ostream &out, std::vector <std::string> & parameters)
|
||||
{
|
||||
df::coord bob = Gui::getCursorPos();
|
||||
out.print("(%d,%d,%d), (%d,%d,%d): canStepBetween = %d, canWalkBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canStepBetween(prev, bob), Maps::canWalkBetween(prev,bob));
|
||||
|
||||
prev = bob;
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
|
||||
#include "Core.h"
|
||||
#include "Console.h"
|
||||
#include "DataDefs.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/World.h"
|
||||
|
||||
#include "df/construction.h"
|
||||
#include "df/game_mode.h"
|
||||
#include "df/map_block.h"
|
||||
#include "df/map_block_column.h"
|
||||
#include "df/world.h"
|
||||
#include "df/z_level_flags.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
command_result infiniteSky (color_ostream &out, std::vector <std::string> & parameters);
|
||||
|
||||
DFHACK_PLUGIN("infiniteSky");
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
commands.push_back(PluginCommand(
|
||||
"infiniteSky",
|
||||
"Creates new sky levels on request, or as you construct up.",
|
||||
infiniteSky, false,
|
||||
"Usage:\n"
|
||||
" infiniteSky\n"
|
||||
" creates one more z-level\n"
|
||||
" infiniteSky [n]\n"
|
||||
" creates n more z-level(s)\n"
|
||||
" infiniteSky enable\n"
|
||||
" enables monitoring of constructions\n"
|
||||
" infiniteSky disable\n"
|
||||
" disable monitoring of constructions\n"
|
||||
"\n"
|
||||
"If construction monitoring is enabled, then the plugin will automatically create new sky z-levels as you construct upward.\n"
|
||||
));
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event) {
|
||||
case SC_GAME_LOADED:
|
||||
// initialize from the world just loaded
|
||||
break;
|
||||
case SC_GAME_UNLOADED:
|
||||
// cleanup
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
*/
|
||||
|
||||
static size_t constructionSize = 0;
|
||||
static bool enabled = false;
|
||||
void doInfiniteSky(color_ostream& out, int32_t howMany);
|
||||
|
||||
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
|
||||
{
|
||||
if ( !enabled )
|
||||
return CR_OK;
|
||||
if ( !Core::getInstance().isMapLoaded() )
|
||||
return CR_OK;
|
||||
{
|
||||
t_gamemodes mode;
|
||||
if ( !World::ReadGameMode(mode) )
|
||||
return CR_FAILURE;
|
||||
if ( mode.g_mode != df::enums::game_mode::DWARF )
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
if ( df::global::world->constructions.size() == constructionSize )
|
||||
return CR_OK;
|
||||
int32_t zNow = df::global::world->map.z_count_block;
|
||||
for ( size_t a = constructionSize; a < df::global::world->constructions.size(); a++ ) {
|
||||
df::construction* construct = df::global::world->constructions[a];
|
||||
if ( construct->pos.z+2 < zNow )
|
||||
continue;
|
||||
doInfiniteSky(out, 1);
|
||||
zNow = df::global::world->map.z_count_block;
|
||||
///break;
|
||||
}
|
||||
constructionSize = df::global::world->constructions.size();
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
void doInfiniteSky(color_ostream& out, int32_t howMany) {
|
||||
df::world* world = df::global::world;
|
||||
CoreSuspender suspend;
|
||||
int32_t x_count_block = world->map.x_count_block;
|
||||
int32_t y_count_block = world->map.y_count_block;
|
||||
for ( int32_t count = 0; count < howMany; count++ ) {
|
||||
//change the size of the pointer stuff
|
||||
int32_t z_count_block = world->map.z_count_block;
|
||||
df::map_block**** block_index = world->map.block_index;
|
||||
for ( int32_t a = 0; a < x_count_block; a++ ) {
|
||||
for ( int32_t b = 0; b < y_count_block; b++ ) {
|
||||
df::map_block** blockColumn = new df::map_block*[z_count_block+1];
|
||||
memcpy(blockColumn, block_index[a][b], z_count_block*sizeof(df::map_block*));
|
||||
blockColumn[z_count_block] = NULL;
|
||||
delete[] block_index[a][b];
|
||||
block_index[a][b] = blockColumn;
|
||||
|
||||
//deal with map_block_column stuff even though it'd probably be fine
|
||||
df::map_block_column* column = world->map.column_index[a][b];
|
||||
if ( !column ) {
|
||||
out.print("%s, line %d: column is null (%d, %d).\n", __FILE__, __LINE__, a, b);
|
||||
continue;
|
||||
}
|
||||
df::map_block_column::T_unmined_glyphs* glyphs = new df::map_block_column::T_unmined_glyphs;
|
||||
glyphs->x[0] = 0;
|
||||
glyphs->x[1] = 1;
|
||||
glyphs->x[2] = 2;
|
||||
glyphs->x[3] = 3;
|
||||
glyphs->y[0] = 0;
|
||||
glyphs->y[1] = 0;
|
||||
glyphs->y[2] = 0;
|
||||
glyphs->y[3] = 0;
|
||||
glyphs->tile[0] = 'e';
|
||||
glyphs->tile[1] = 'x';
|
||||
glyphs->tile[2] = 'p';
|
||||
glyphs->tile[3] = '^';
|
||||
column->unmined_glyphs.push_back(glyphs);
|
||||
}
|
||||
}
|
||||
df::z_level_flags* flags = new df::z_level_flags[z_count_block+1];
|
||||
memcpy(flags, world->map.z_level_flags, z_count_block*sizeof(df::z_level_flags));
|
||||
flags[z_count_block].whole = 0;
|
||||
flags[z_count_block].bits.update = 1;
|
||||
world->map.z_count_block++;
|
||||
world->map.z_count++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
command_result infiniteSky (color_ostream &out, std::vector <std::string> & parameters)
|
||||
{
|
||||
if ( parameters.size() > 1 )
|
||||
return CR_WRONG_USAGE;
|
||||
if ( parameters.size() == 0 ) {
|
||||
out.print("Construction monitoring is %s.\n", enabled ? "enabled" : "disabled");
|
||||
return CR_OK;
|
||||
}
|
||||
if (parameters[0] == "enable") {
|
||||
enabled = true;
|
||||
out.print("Construction monitoring enabled.\n");
|
||||
return CR_OK;
|
||||
}
|
||||
if (parameters[0] == "disable") {
|
||||
enabled = false;
|
||||
out.print("Construction monitoring disabled.\n");
|
||||
constructionSize = 0;
|
||||
return CR_OK;
|
||||
}
|
||||
int32_t howMany = 0;
|
||||
howMany = atoi(parameters[0].c_str());
|
||||
out.print("InfiniteSky: creating %d new z-level%s of sky.\n", howMany, howMany == 1 ? "" : "s" );
|
||||
doInfiniteSky(out, howMany);
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
|
||||
#include "Core.h"
|
||||
#include "Console.h"
|
||||
#include "DataDefs.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/EventManager.h"
|
||||
|
||||
#include "df/caste_raw.h"
|
||||
#include "df/creature_raw.h"
|
||||
#include "df/syndrome.h"
|
||||
#include "df/unit.h"
|
||||
#include "df/unit_syndrome.h"
|
||||
#include "df/world.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace std;
|
||||
|
||||
DFHACK_PLUGIN("trueTransformation");
|
||||
|
||||
void syndromeHandler(color_ostream& out, void* ptr);
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
EventManager::EventHandler syndrome(syndromeHandler, 1);
|
||||
Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("trueTransformation");
|
||||
EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, me);
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
void syndromeHandler(color_ostream& out, void* ptr) {
|
||||
EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr;
|
||||
//out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex);
|
||||
|
||||
int32_t index = df::unit::binsearch_index(df::global::world->units.active, data->unitId);
|
||||
if ( index < 0 ) {
|
||||
out.print("%s, line %d: couldn't find unit.\n", __FILE__, __LINE__);
|
||||
return;
|
||||
}
|
||||
df::unit* unit = df::global::world->units.active[index];
|
||||
df::unit_syndrome* unit_syndrome = unit->syndromes.active[data->syndromeIndex];
|
||||
df::syndrome* syndrome = df::global::world->raws.syndromes.all[unit_syndrome->type];
|
||||
|
||||
bool foundIt = false;
|
||||
int32_t raceId = -1;
|
||||
df::creature_raw* creatureRaw = NULL;
|
||||
int32_t casteId = -1;
|
||||
for ( size_t a = 0; a < syndrome->syn_class.size(); a++ ) {
|
||||
if ( *syndrome->syn_class[a] == "\\PERMANENT" ) {
|
||||
foundIt = true;
|
||||
}
|
||||
if ( foundIt && raceId == -1 ) {
|
||||
//find the race with the name
|
||||
string& name = *syndrome->syn_class[a];
|
||||
for ( size_t b = 0; b < df::global::world->raws.creatures.all.size(); b++ ) {
|
||||
df::creature_raw* creature = df::global::world->raws.creatures.all[b];
|
||||
if ( creature->creature_id != name )
|
||||
continue;
|
||||
raceId = b;
|
||||
creatureRaw = creature;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ( foundIt && raceId != -1 ) {
|
||||
string& name = *syndrome->syn_class[a];
|
||||
for ( size_t b = 0; b < creatureRaw->caste.size(); b++ ) {
|
||||
df::caste_raw* caste = creatureRaw->caste[b];
|
||||
if ( caste->caste_id != name )
|
||||
continue;
|
||||
casteId = b;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !foundIt || raceId == -1 || casteId == -1 )
|
||||
return;
|
||||
|
||||
unit->enemy.normal_race = raceId;
|
||||
unit->enemy.normal_caste = casteId;
|
||||
//that's it!
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
#include "Core.h"
|
||||
#include "Console.h"
|
||||
#include "Export.h"
|
||||
#include "PluginManager.h"
|
||||
#include "DataDefs.h"
|
||||
#include "modules/World.h"
|
||||
#include "df/global_objects.h"
|
||||
|
||||
#include <vector>
|
||||
using namespace std;
|
||||
using namespace DFHack;
|
||||
|
||||
DFHACK_PLUGIN("workNow");
|
||||
|
||||
static bool active = false;
|
||||
|
||||
DFhackCExport command_result workNow(color_ostream& out, vector<string>& parameters);
|
||||
|
||||
DFhackCExport command_result plugin_init(color_ostream& out, std::vector<PluginCommand> &commands) {
|
||||
commands.push_back(PluginCommand("workNow", "makes dwarves look for jobs every time you pause", workNow, false, "When workNow is active, every time the game pauses, DF will make dwarves perform any appropriate available jobs. This includes when you one step through the game using the pause menu.\n"
|
||||
"workNow 1\n"
|
||||
" activate workNow\n"
|
||||
"workNow 0\n"
|
||||
" deactivate workNow\n"));
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) {
|
||||
active = false;
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event e) {
|
||||
if ( !active )
|
||||
return CR_OK;
|
||||
if ( e == DFHack::SC_WORLD_UNLOADED ) {
|
||||
active = false;
|
||||
return CR_OK;
|
||||
}
|
||||
if ( e != DFHack::SC_PAUSED )
|
||||
return CR_OK;
|
||||
|
||||
*df::global::process_jobs = true;
|
||||
*df::global::process_dig = true;
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
DFhackCExport command_result workNow(color_ostream& out, vector<string>& parameters) {
|
||||
if ( parameters.size() == 0 ) {
|
||||
out.print("workNow status = %s\n", active ? "active" : "inactive");
|
||||
return CR_OK;
|
||||
}
|
||||
if ( parameters.size() > 1 ) {
|
||||
return CR_WRONG_USAGE;
|
||||
}
|
||||
int32_t a = atoi(parameters[0].c_str());
|
||||
|
||||
if (a < 0 || a > 1)
|
||||
return CR_WRONG_USAGE;
|
||||
|
||||
active = (bool)a;
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue