diff --git a/.gitignore b/.gitignore index 9f2b009c6..b4a578ec0 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ dfhack/python/dist build/CPack*Config.cmake /cmakeall.bat + +# vim swap files +*.swp diff --git a/NEWS b/NEWS index f7a7a6281..f2a547e10 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,8 @@ DFHack future Internals: - support for displaying active keybindings properly. - support for reusable widgets in lua screen library. + - Maps::canStepBetween: returns whether you can walk between two tiles in one step. + - EventManager: monitors various in game events centrally so that individual plugins don't have to monitor the same things redundantly. Notable bugfixes: - autobutcher can be re-enabled again after being stopped. - stopped Dwarf Manipulator from unmasking vampires. @@ -62,7 +64,12 @@ DFHack future Reworked to make use of lua modules, now all the scripts can be used from other scripts. New Eventful plugin: A collection of lua events, that will allow new ways to interact with df world. - + Auto syndrome plugin: a way of automatically applying boiling rock syndromes and calling dfhack commands controlled by raws. + Infinite sky plugin: create new z-levels automatically or on request. + True transformation plugin: a better way of doing permanent transformations that allows later transformations. + Work now plugin: makes the game assign jobs every time you pause. + + DFHack v0.34.11-r2 Internals: diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 784b54c90..64dafd542 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -111,6 +111,7 @@ include/modules/Burrows.h include/modules/Constructions.h include/modules/Units.h include/modules/Engravings.h +include/modules/EventManager.h include/modules/Gui.h include/modules/Items.h include/modules/Job.h @@ -132,6 +133,7 @@ modules/Burrows.cpp modules/Constructions.cpp modules/Units.cpp modules/Engravings.cpp +modules/EventManager.cpp modules/Gui.cpp modules/Items.cpp modules/Job.cpp diff --git a/library/Core.cpp b/library/Core.cpp index 7a0186362..9df7c1be4 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -44,6 +44,7 @@ using namespace std; #include "VersionInfo.h" #include "PluginManager.h" #include "ModuleFactory.h" +#include "modules/EventManager.h" #include "modules/Gui.h" #include "modules/World.h" #include "modules/Graphic.h" @@ -947,6 +948,7 @@ bool Core::Init() cerr << "Initializing Plugins.\n"; // create plugin manager plug_mgr = new PluginManager(this); + plug_mgr->init(this); IODATA *temp = new IODATA; temp->core = this; temp->plug_mgr = plug_mgr; @@ -1281,6 +1283,8 @@ static int buildings_timer = 0; void Core::onUpdate(color_ostream &out) { + EventManager::manageEvents(out); + // convert building reagents if (buildings_do_onupdate && (++buildings_timer & 1)) buildings_onUpdate(out); @@ -1294,6 +1298,8 @@ void Core::onUpdate(color_ostream &out) void Core::onStateChange(color_ostream &out, state_change_event event) { + EventManager::onStateChange(out, event); + buildings_onStateChange(out, event); plug_mgr->OnStateChange(out, event); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 0c80639b4..86bab66cd 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -22,6 +22,7 @@ must not be misrepresented as being the original software. distribution. */ +#include "modules/EventManager.h" #include "Internal.h" #include "Core.h" #include "MemAccess.h" @@ -270,6 +271,7 @@ bool Plugin::unload(color_ostream &con) // if we are actually loaded if(state == PS_LOADED) { + EventManager::unregisterAll(this); // notify the plugin about an attempt to shutdown if (plugin_onstatechange && plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND) @@ -598,6 +600,22 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn) } PluginManager::PluginManager(Core * core) +{ + cmdlist_mutex = new mutex(); + eval_ruby = NULL; +} + +PluginManager::~PluginManager() +{ + for(size_t i = 0; i < all_plugins.size();i++) + { + delete all_plugins[i]; + } + all_plugins.clear(); + delete cmdlist_mutex; +} + +void PluginManager::init(Core * core) { #ifdef LINUX_BUILD string path = core->getHackPath() + "plugins/"; @@ -606,8 +624,6 @@ PluginManager::PluginManager(Core * core) string path = core->getHackPath() + "plugins\\"; const string searchstr = ".plug.dll"; #endif - cmdlist_mutex = new mutex(); - eval_ruby = NULL; vector filez; getdir(path, filez); for(size_t i = 0; i < filez.size();i++) @@ -622,16 +638,6 @@ PluginManager::PluginManager(Core * core) } } -PluginManager::~PluginManager() -{ - for(size_t i = 0; i < all_plugins.size();i++) - { - delete all_plugins[i]; - } - all_plugins.clear(); - delete cmdlist_mutex; -} - Plugin *PluginManager::getPluginByName (const std::string & name) { for(size_t i = 0; i < all_plugins.size(); i++) diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 9ef16703a..62a195867 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -205,6 +205,7 @@ namespace DFHack friend class Plugin; PluginManager(Core * core); ~PluginManager(); + void init(Core* core); void OnUpdate(color_ostream &out); void OnStateChange(color_ostream &out, state_change_event event); void registerCommands( Plugin * p ); diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 53852efbb..9bb5c7e54 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -25,7 +25,9 @@ distribution. #pragma once #include "Export.h" #include "DataDefs.h" +#include "Types.h" #include "df/building.h" +#include "df/building_type.h" #include "df/civzone_type.h" #include "df/furnace_type.h" #include "df/workshop_type.h" @@ -181,5 +183,8 @@ DFHACK_EXPORT bool constructWithFilters(df::building *bld, std::vector +#include +#include +#include #include +#include #include -#include using namespace std; +#include "ColorText.h" #include "VersionInfo.h" #include "MemAccess.h" #include "Types.h" @@ -77,6 +82,14 @@ using df::global::building_next_id; using df::global::process_jobs; using df::building_def; +struct CoordHash { + size_t operator()(const df::coord pos) const { + return pos.x*65537 + pos.y*17 + pos.z; + } +}; + +static unordered_map locationToBuilding; + static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile) { if (!extent.extents) @@ -236,6 +249,31 @@ df::building *Buildings::findAtTile(df::coord pos) if (!occ || !occ->bits.building) return NULL; + auto a = locationToBuilding.find(pos); + if ( a == locationToBuilding.end() ) { + cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <buildings.all, id); + if ( index == -1 ) { + cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <buildings.all[index]; + if (!building->isSettingOccupancy()) + return NULL; + + if (building->room.extents && building->isExtentShaped()) + { + auto etile = getExtentTile(building->room, pos); + if (!etile || !*etile) + return NULL; + } + return building; + + /* + //old method: brute-force auto &vec = df::building::get_vector(); for (size_t i = 0; i < vec.size(); i++) { @@ -260,6 +298,7 @@ df::building *Buildings::findAtTile(df::coord pos) } return NULL; + */ } bool Buildings::findCivzonesAt(std::vector *pvec, df::coord pos) @@ -1077,3 +1116,52 @@ bool Buildings::deconstruct(df::building *bld) return true; } +static unordered_map corner1; +static unordered_map corner2; + +void Buildings::clearBuildings(color_ostream& out) { + corner1.clear(); + corner2.clear(); + locationToBuilding.clear(); +} + +void Buildings::updateBuildings(color_ostream& out, void* ptr) { + //out.print("Updating buildings, %s %d\n", __FILE__, __LINE__); + int32_t id = (int32_t)ptr; + + if ( corner1.find(id) == corner1.end() ) { + //new building: mark stuff + int32_t index = df::building::binsearch_index(df::global::world->buildings.all, id); + if ( index == -1 ) { + out.print("%s, line %d: Couldn't find new building id=%d.\n", __FILE__, __LINE__, id); + exit(1); + } + df::building* building = df::global::world->buildings.all[index]; + df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z); + df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z); + + corner1[id] = p1; + corner2[id] = p2; + + for ( int32_t x = p1.x; x <= p2.x; x++ ) { + for ( int32_t y = p1.y; y <= p2.y; y++ ) { + df::coord pt(x,y,building->z); + locationToBuilding[pt] = id; + } + } + } else { + //existing building: destroy it + df::coord p1 = corner1[id]; + df::coord p2 = corner2[id]; + + for ( int32_t x = p1.x; x <= p2.x; x++ ) { + for ( int32_t y = p1.y; y <= p2.y; y++ ) { + df::coord pt(x,y,p1.z); + locationToBuilding.erase(pt); + } + } + + corner1.erase(id); + corner2.erase(id); + } +} diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp new file mode 100644 index 000000000..3af096004 --- /dev/null +++ b/library/modules/EventManager.cpp @@ -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 +#include +#include + +using namespace std; +using namespace DFHack; +using namespace EventManager; + +/* + * TODO: + * error checking + * consider a typedef instead of a struct for EventHandler + **/ + +//map > tickQueue; +multimap tickQueue; + +//TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever +multimap 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, 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(tick+(uint32_t)when, handler)); + handlers[EventType::TICK].insert(pair(plugin,handler)); + return; +} + +void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) { + for ( multimap::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 prevJobs; + +//unit death +static unordered_set livingUnits; + +//item creation +static int32_t nextItem; + +//building +static int32_t nextBuilding; +static unordered_set buildings; + +//construction +static unordered_set 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 newTickQueue; + for ( auto i = tickQueue.begin(); i != tickQueue.end(); i++ ) { + newTickQueue.insert(pair(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 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 copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end()); + map 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::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 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 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 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 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 constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end()); + + multimap 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 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 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); + } +} + diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 5a8a8c7a4..c6469657c 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -55,7 +55,7 @@ using namespace std; using namespace DFHack; using namespace df::enums; -df::job *DFHack::Job::cloneJobStruct(df::job *job) +df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepWorkerData) { CHECK_NULL_POINTER(job); @@ -76,7 +76,7 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job) { df::general_ref *ref = pnew->general_refs[i]; - if (virtual_cast(ref)) + if (!keepWorkerData && virtual_cast(ref)) vector_erase_at(pnew->general_refs, i); else pnew->general_refs[i] = ref->clone(); diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 38f8bfb9f..43b76904a 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -30,10 +30,12 @@ distribution. #include #include #include +#include using namespace std; #include "modules/Maps.h" #include "modules/MapCache.h" +#include "ColorText.h" #include "Error.h" #include "VersionInfo.h" #include "MemAccess.h" @@ -60,6 +62,7 @@ using namespace std; #include "df/region_map_entry.h" #include "df/flow_info.h" #include "df/plant.h" +#include "df/building_type.h" using namespace DFHack; using namespace df::enums; @@ -536,6 +539,129 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) return tile1 && tile1 == tile2; } +bool Maps::canStepBetween(df::coord pos1, df::coord pos2) +{ + color_ostream& out = Core::getInstance().getConsole(); + int32_t dx = pos2.x-pos1.x; + int32_t dy = pos2.y-pos1.y; + int32_t dz = pos2.z-pos1.z; + + if ( dx*dx > 1 || dy*dy > 1 || dz*dz > 1 ) + return false; + + if ( pos2.z < pos1.z ) { + df::coord temp = pos1; + pos1 = pos2; + pos2 = temp; + } + + df::map_block* block1 = getTileBlock(pos1); + df::map_block* block2 = getTileBlock(pos2); + + if ( !block1 || !block2 ) + return false; + + if ( !index_tile(block1->walkable,pos1) || !index_tile(block2->walkable,pos2) ) { + return false; + } + + if ( dz == 0 ) + return true; + + df::tiletype* type1 = Maps::getTileType(pos1); + df::tiletype* type2 = Maps::getTileType(pos2); + + df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type1); + df::tiletype_shape shape2 = ENUM_ATTR(tiletype,shape,*type2); + + if ( dx == 0 && dy == 0 ) { + //check for forbidden hatches and floors and such + df::enums::tile_building_occ::tile_building_occ upOcc = index_tile(block2->occupancy,pos2).bits.building; + if ( upOcc == df::enums::tile_building_occ::Impassable || upOcc == df::enums::tile_building_occ::Obstacle || upOcc == df::enums::tile_building_occ::Floored ) + return false; + + if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == shape1 ) + return true; + if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == tiletype_shape::STAIR_DOWN ) + return true; + if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_UPDOWN ) + return true; + if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_DOWN ) + return true; + if ( shape1 == tiletype_shape::RAMP && shape2 == tiletype_shape::RAMP_TOP ) { + //it depends + //there has to be a wall next to the ramp + bool foundWall = false; + for ( int32_t x = -1; x <= 1; x++ ) { + for ( int32_t y = -1; y <= 1; y++ ) { + if ( x == 0 && y == 0 ) + continue; + df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z)); + df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type); + if ( shape1 == tiletype_shape::WALL ) { + foundWall = true; + x = 2; + break; + } + } + } + if ( !foundWall ) + return false; //unusable ramp + + //there has to be an unforbidden hatch above the ramp + if ( index_tile(block2->occupancy,pos2).bits.building != df::enums::tile_building_occ::Dynamic ) + return false; + //note that forbidden hatches have Floored occupancy. unforbidden ones have dynamic occupancy + df::building* building = Buildings::findAtTile(pos2); + if ( building == NULL ) { + out << __FILE__ << ", line " << __LINE__ << ": couldn't find hatch.\n"; + return false; + } + if ( building->getType() != df::enums::building_type::Hatch ) { + return false; + } + return true; + } + return false; + } + + //diagonal up: has to be a ramp + if ( shape1 == tiletype_shape::RAMP /*&& shape2 == tiletype_shape::RAMP*/ ) { + df::coord up = df::coord(pos1.x,pos1.y,pos1.z+1); + bool foundWall = false; + for ( int32_t x = -1; x <= 1; x++ ) { + for ( int32_t y = -1; y <= 1; y++ ) { + if ( x == 0 && y == 0 ) + continue; + df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z)); + df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type); + if ( shape1 == tiletype_shape::WALL ) { + foundWall = true; + x = 2; + break; + } + } + } + if ( !foundWall ) + return false; //unusable ramp + df::tiletype* typeUp = Maps::getTileType(up); + df::tiletype_shape shapeUp = ENUM_ATTR(tiletype,shape,*typeUp); + if ( shapeUp != tiletype_shape::RAMP_TOP ) + return false; + + df::map_block* blockUp = getTileBlock(up); + if ( !blockUp ) + return false; + + df::enums::tile_building_occ::tile_building_occ occupancy = index_tile(blockUp->occupancy,up).bits.building; + if ( occupancy == df::enums::tile_building_occ::Obstacle || occupancy == df::enums::tile_building_occ::Floored || occupancy == df::enums::tile_building_occ::Impassable ) + return false; + return true; + } + + return false; +} + #define COPY(a,b) memcpy(&a,&b,sizeof(a)) MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) diff --git a/library/xml b/library/xml index 42e26b368..55cb62822 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 42e26b368f48a148aba07fea295c6d19bca3fcbc +Subproject commit 55cb628224f9da7d39e88e62a312d877aeed537f diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index b72739228..f2bf4d4a4 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -131,7 +131,11 @@ if (BUILD_SUPPORTED) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) + DFHACK_PLUGIN(workNow workNow.cpp) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) + DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp) + DFHACK_PLUGIN(trueTransformation trueTransformation.cpp) + DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) endif() diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp new file mode 100644 index 000000000..79e8b35ec --- /dev/null +++ b/plugins/autoSyndrome.cpp @@ -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 +#include +#include +#include + +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& 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 &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& 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 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; +} + diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 134d5cb67..8a1edb9cc 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,6 +18,9 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) +DFHACK_PLUGIN(eventExample eventExample.cpp) +DFHACK_PLUGIN(printArgs printArgs.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() +DFHACK_PLUGIN(stepBetween stepBetween.cpp) diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp new file mode 100644 index 000000000..3a2970c5b --- /dev/null +++ b/plugins/devel/eventExample.cpp @@ -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 + +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& parameters); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand("eventExample", "Sets up a few event triggers.",eventExample)); + return CR_OK; +} + +command_result eventExample(color_ostream& out, vector& 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); +} + diff --git a/plugins/devel/printArgs.cpp b/plugins/devel/printArgs.cpp new file mode 100644 index 000000000..051c7b1dc --- /dev/null +++ b/plugins/devel/printArgs.cpp @@ -0,0 +1,32 @@ + +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include + +using namespace DFHack; +using namespace std; + +command_result printArgs (color_ostream &out, std::vector & parameters); + +DFHACK_PLUGIN("printArgs"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "printArgs", "Print the arguments given.", + printArgs, false + )); + return CR_OK; +} + +command_result printArgs (color_ostream &out, std::vector & parameters) +{ + for ( size_t a = 0; a < parameters.size(); a++ ) { + out << "Argument " << (a+1) << ": \"" << parameters[a] << "\"" << endl; + } + return CR_OK; +} diff --git a/plugins/devel/stepBetween.cpp b/plugins/devel/stepBetween.cpp new file mode 100644 index 000000000..32d5be31b --- /dev/null +++ b/plugins/devel/stepBetween.cpp @@ -0,0 +1,87 @@ + +#include "Core.h" +#include +#include +#include + +// 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 & 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 &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 & 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; +} diff --git a/plugins/infiniteSky.cpp b/plugins/infiniteSky.cpp new file mode 100644 index 000000000..1c99df6d0 --- /dev/null +++ b/plugins/infiniteSky.cpp @@ -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 +#include +#include + +using namespace std; + +using namespace DFHack; +using namespace df::enums; + +command_result infiniteSky (color_ostream &out, std::vector & parameters); + +DFHACK_PLUGIN("infiniteSky"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &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 & 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; +} diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp new file mode 100644 index 000000000..6c4245da8 --- /dev/null +++ b/plugins/trueTransformation.cpp @@ -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 + +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 &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! +} + diff --git a/plugins/workNow.cpp b/plugins/workNow.cpp new file mode 100644 index 000000000..5058259f5 --- /dev/null +++ b/plugins/workNow.cpp @@ -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 +using namespace std; +using namespace DFHack; + +DFHACK_PLUGIN("workNow"); + +static bool active = false; + +DFhackCExport command_result workNow(color_ostream& out, vector& parameters); + +DFhackCExport command_result plugin_init(color_ostream& out, std::vector &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& 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; +} + +