From e45fbfc61d33c470ebea12966aa13a4ee2ac344d Mon Sep 17 00:00:00 2001 From: Michael Casadevall Date: Sun, 5 Jun 2016 09:09:07 -0400 Subject: [PATCH] Modified dwaftvet plugin to work with latest DFHack Initial commit of the dwarfvet plugin Signed-off-by: Michael Casadevall --- library/include/modules/Buildings.h | 2 + library/modules/Buildings.cpp | 14 + plugins/CMakeLists.txt | 1 + plugins/dwarfvet.cpp | 835 ++++++++++++++++++++++++++++ 4 files changed, 852 insertions(+) create mode 100644 plugins/dwarfvet.cpp diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 1d939665f..de78acda6 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -292,6 +292,8 @@ DFHACK_EXPORT bool isActivityZone(df::building * building); DFHACK_EXPORT bool isPenPasture(df::building * building); DFHACK_EXPORT bool isPitPond(df::building * building); DFHACK_EXPORT bool isActive(df::building * building); +DFHACK_EXPORT bool isHospital(df::building * building); +DFHACK_EXPORT bool isAnimalTraining(df::building * building); DFHACK_EXPORT df::building* findPenPitAt(df::coord coord); } diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index a6589bac1..b1a22e2cc 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -1263,6 +1263,20 @@ bool Buildings::isActive(df::building * building) return ((df::building_civzonest*) building)->zone_flags.bits.active != 0; } +bool Buildings::isHospital(df::building * building) + { + if (!isActivityZone(building)) + return false; + return ((df::building_civzonest*) building)->zone_flags.bits.hospital != 0; + } + + bool Buildings::isAnimalTraining(df::building * building) + { + if (!isActivityZone(building)) + return false; + return ((df::building_civzonest*) building)->zone_flags.bits.animal_training != 0; + } + // returns building of pen/pit at cursor position (NULL if nothing found) df::building* Buildings::findPenPitAt(df::coord coord) { diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index cc0de019f..fbf33f459 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -93,6 +93,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(dig dig.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp) add_subdirectory(diggingInvaders) + DFHACK_PLUGIN(dwarfvet dwarfvet.cpp) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(embark-tools embark-tools.cpp) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp new file mode 100644 index 000000000..44b3684e0 --- /dev/null +++ b/plugins/dwarfvet.cpp @@ -0,0 +1,835 @@ +/** + * Copyright (c) 2015, Michael Casadevall + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + **/ + +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" +#include "modules/EventManager.h" +#include "modules/Units.h" +#include "modules/Buildings.h" +#include "modules/Maps.h" +#include "modules/Job.h" + +#include "df/animal_training_level.h" +#include "df/building_type.h" +#include "df/caste_raw.h" +#include "df/caste_raw_flags.h" +#include "df/creature_raw.h" +#include "df/job.h" +#include "df/general_ref_unit_workerst.h" +#include "df/profession.h" +#include "df/ui.h" +#include "df/unit.h" +#include "df/unit_health_info.h" +#include "df/unit_health_flags.h" +#include "df/world.h" + +#include +#include + +using namespace DFHack; +using namespace DFHack::Units; +using namespace DFHack::Buildings; + +using namespace std; + +DFHACK_PLUGIN("dwarfvet"); +DFHACK_PLUGIN_IS_ENABLED(dwarfvet_enabled); + +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(world); + +static vector tracked_units; +static int32_t howOften = 100; + +struct hospital_spot { + int32_t x; + int32_t y; + int32_t z; +}; + +class Patient { + public: + // Constructor/Deconstrctor + Patient(int32_t id, int spot_index, int32_t x, int32_t y, int32_t z); + int32_t getID() { return this->id; }; + int32_t getSpotIndex() { return this->spot_index; }; + int32_t returnX() { return this->spot_in_hospital.x; }; + int32_t returnY() { return this->spot_in_hospital.y; }; + int32_t returnZ() { return this->spot_in_hospital.z; }; + + private: + struct hospital_spot spot_in_hospital; + int id; + int spot_index; +}; + +Patient::Patient(int32_t id, int32_t spot_index, int32_t x, int32_t y, int32_t z){ + this->id = id; + this->spot_in_hospital.x = x; + this->spot_in_hospital.y = y; + this->spot_in_hospital.z = z; +} + +class AnimalHospital { + + public: + // Constructor + AnimalHospital(df::building *, color_ostream &out); + ~AnimalHospital(); + int32_t getID() { return id; } + bool acceptPatient(int32_t id, color_ostream&); + void processPatients(color_ostream &out); + void dischargePatient(Patient * patient, color_ostream &out); + void calculateHospital(bool force, color_ostream &out); + void reportUsage(color_ostream &out); + + // GC + bool to_be_deleted; + + private: + int spots_open; + int32_t id; + int32_t x1; + int32_t x2; + int32_t y1; + int32_t y2; + int32_t z; + int height; + int length; + + // Doing an actual array in C++ is *annoying*, bloody copy constructors */ + vector spots_in_use; + vector building_in_hospital_notification; /* If present, we already notified about this */ + vector accepted_patients; +}; + +AnimalHospital::AnimalHospital(df::building * building, color_ostream &out) { + // Copy in what we need to know + id = building->id; + x1 = building->x1; + x2 = building->x2; + y1 = building->y1; + y2 = building->y2; + z = building->z; + + // Determine how many spots we have for animals + this->length = x2-x1; + this->height = y2-y1; + + // And calculate the hospital! + this->calculateHospital(true, out); +} + +AnimalHospital::~AnimalHospital() { + // Go through and delete all the patients + for (vector::iterator accepted_patient = this->accepted_patients.begin(); accepted_patient != this->accepted_patients.end(); accepted_patient++) { + delete (*accepted_patient); + } +} + +bool AnimalHospital::acceptPatient(int32_t id, color_ostream &out) { + // This function determines if we can accept a patient, and if we will. + this->calculateHospital(true, out); + + // First, do we have room? + if (!spots_open) return false; + + // Yup, let's find the next open spot, + // and give it to our patient + int spot_cur = 0; // fuck the STL for requiring a second counter to make this usable + for (vector::iterator spot = this->spots_in_use.begin(); spot != this->spots_in_use.end(); spot++) { + if (*spot == false) { + *spot = true; + break; + } + spot_cur++; + } + + spots_open--; + + // Convert the spot into x/y/z cords. + int offset_y = spot_cur/length; + int offset_x = spot_cur%length; + + // Create the patient! + Patient * patient = new Patient(id, + spot_cur, + this->x1+offset_x, + this->y1+offset_y, + this->z + ); + + accepted_patients.push_back(patient); + return true; +} + +// Before any use of the hospital, we need to make calculate open spots +// and such. This can change (i.e. stuff built in hospital) and +// such so it should be called on each function. +void AnimalHospital::reportUsage(color_ostream &out) { + // Debugging tool to see parts of the hospital in use + int length_cursor = this->length; + + for (vector::iterator spot = this->spots_in_use.begin(); spot != this->spots_in_use.end(); spot++) { + if (*spot) out.print("t"); + if (!(*spot)) out.print("f"); + length_cursor--; + if (length_cursor < 0) { + out.print("\n"); + length_cursor = this->length; + } + } + out.print("\n"); + +} + +void AnimalHospital::calculateHospital(bool force, color_ostream &out) { + // Only calculate out the hospital if we actually have a patient in it + // (acceptPatient will forcibly rerun this to make sure everything OK + + // Should reduce FPS impact of each calculation tick when the hospitals + // are not in use + //if (!force || (spots_open == length*height)) { + // Hospital is idle, don't recalculate + // return; + //} + + // Calculate out the total area of the hospital + // This can change if a hospital has been resized + this->spots_open = length*height; + this->spots_in_use.assign(this->spots_open, false); + + // The spots_in_use maps one to one with a spot + // starting at the upper-left hand corner, then + // across, then down. i.e. + // + // given hospital zone: + // + // UU + // uU + // + // where U is in use, and u isn't, the array + // would be t,t,f,t + + // Walk the building array and see what stuff is in the hospital, + // then walk the patient array and remark those spots as used. + + // If a patient is in an invalid spot, reassign it + for (size_t b =0 ; b < world->buildings.all.size(); b++) { + df::building* building = world->buildings.all[b]; + + // Check that we're not comparing ourselves; + if (building->id == this->id) { + continue; + } + + // Check if the building is on our z level, if it isn't + // then it can't overlap the hospital (until Toady implements + // multi-z buildings + if (building->z != this->z) { + continue; + } + + // DF defines activity zones multiple times in the building structure + // If axises agree with each other, we're looking at a reflection of + // ourselves + if (building->x1 == this->x1 && + building->x2 == this->x2 && + building->y1 == this->y1 && + building->y2 == this->y2) { + continue; + } + + // Check for X/Y overlap + // I can't believe I had to look this up -_-; + // http://stackoverflow.com/questions/306316/determine-if-two-rectangles-overlap-each-other + if ((this->x1 > building->x2 || + building->x1 > this->x2 || + this->y1 > building->y2 || + building->y1 > this->y2)) { + continue; + } + + // Crap, building overlaps, we need to figure out where it is in the hospital + // NOTE: under some conditions, this generates a false warning. Not a lot I can do about it + + // Mark spots used by that building as used; FIXME: handle special logic for traction benches and such + int building_offset_x = building->x1 - this->x1; + int building_offset_y = building->y1 - this->y1; + int building_length = building->x2 - building->x1 + 1; + int building_height = building->y2 - building->y1 + 1; + + // Cap the used calculation to only include the part in the hospital + if (this->x1 > building->x1) { + building_offset_x -= (this->x1 - building->x1); + } + + if (this->y1 > building->y1) { + building_offset_y -= (building->y1 - this->y1); + } + + if ((this->x2 < building->x2) && building_offset_x) { + building_length -= (this->x2 - building->x2) + 1; + } + + if ((this->y2 < building->y2) && building_offset_y) { + building_height = (building->y2 - this->y2) + 1; + } + + // Quick explination, if a building is north or east of the activity zone, + // we get a negative offset, we'll just skip those lines below. If its + // south or west, we make the building length/height lower to compinsate. + + /* if we have a negative x offset, we correct that */ + if (building_offset_x < 0) { + building_height += building_offset_x; + building_offset_x = 0; + } + + /* Handle negative y offset */ + if (building_offset_y < 0) { + building_length += building_offset_y; + building_offset_y = 0; + }; + + /* Advance the pointer to first row we need to mark */ + int spot_cur = 0; + if (building_offset_y) { + spot_cur = (length+1) * building_offset_y; + } + + spot_cur += building_offset_x; + /* Start marking! */ + for (int i = 0; i != building_height; i++) { + for (int j = 0; j != building_length; j++) { + spots_in_use[spot_cur+j] = true; + } + + // Wind the cursor to the start of the next row + spot_cur += length+1; + } + + // *phew*, done. Now repeat the process for the next building! + } + +} + +// Self explanatory +void AnimalHospital::dischargePatient(Patient * patient, color_ostream &out) { + int32_t id = patient->getID(); + + // Remove them from the hospital + + // We can safely iterate here because once we delete the unit + // we no longer use the iterator + for (vector::iterator accepted_patient = this->accepted_patients.begin(); accepted_patient != this->accepted_patients.end(); accepted_patient++) { + if ( (*accepted_patient)->getID() == id) { + out.print("Discharging unit %d from hospital %d\n", id, this->id); + // Reclaim their spot + spots_in_use[(*accepted_patient)->getSpotIndex()] = false; + this->spots_open++; + delete (*accepted_patient); + this->accepted_patients.erase(accepted_patient); + break; + } + } + + // And the master list + for (vector::iterator it = tracked_units.begin(); it != tracked_units.end(); it++) { + if ((*it) == id) { + tracked_units.erase(it); + break; + } + } + + return; +} + +void AnimalHospital::processPatients(color_ostream &out) { + // Where the magic happens + for (vector::iterator patient = this->accepted_patients.begin(); patient != this->accepted_patients.end(); patient++) { + int id = (*patient)->getID(); + df::unit * real_unit; + // Appears the health bits can get freed/realloced too -_-;, Find the unit from the main + // index and check it there. + auto units = world->units.all; + + for ( size_t a = 0; a < units.size(); a++ ) { + real_unit = units[a]; + if (real_unit->id == id) { + break; + } + } + + // Check to make sure the unit hasn't expired before assigning a job, or if they've been healed + if (real_unit->flags1.bits.dead || !real_unit->health->flags.bits.needs_healthcare) { + // discharge the patient from the hospital + this->dischargePatient(*patient, out); + return; + } + + // Give the unit a job if they don't have any + if (!real_unit->job.current_job) { + // Create REST struct + df::job * job = new df::job; + DFHack::Job::linkIntoWorld(job); + + job->pos.x = (*patient)->returnX(); + job->pos.y = (*patient)->returnY(); + job->pos.z = (*patient)->returnZ(); + job->flags.bits.special = 1; + job->job_type = df::enums::job_type::Rest; + df::general_ref *ref = df::allocate(); + ref->setID(real_unit->id); + job->general_refs.push_back(ref); + real_unit->job.current_job = job; + job->wait_timer = 1600; + out.print("Telling intelligent unit %d to report to the hospital!\n", real_unit->id); + } + } +} + + +static vector animal_hospital_zones; + +void delete_animal_hospital_vector(color_ostream &out) { + out.print("Clearing all animal hospitals\n"); + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + delete (*animal_hospital); + } + animal_hospital_zones.clear(); +} + +command_result dwarfvet(color_ostream &out, std::vector & parameters); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "dwarfvet", + "Allows animals to be cared for in animal hospitals (activity zones that are animal training + hospital combined).", + dwarfvet, + false, //allow non-interactive use + "dwarfvet enable\n" + " enables animals to use animal hospitals (requires dwarf with Animal Caretaker labor enabled)\n" + "dwarfvet report\n" + " displays all zones dwarfvet considers animal hospitals and their current location on the map\n" + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +bool isActiveAnimalHospital(df::building * building) { + if (Buildings::isHospital(building) && Buildings::isAnimalTraining(building) && Buildings::isActive(building)) { + return true; + } + + return false; +} + +bool compareAnimalHospitalZones(df::building * hospital1, df::building * hospital2) { + // We compare hospitals by checking if positions are identical, not by ID + // since activity zones can easily be changed in size + + if ( hospital1->x1 == hospital2->x1 && + hospital1->x2 == hospital2->x2 && + hospital1->y1 == hospital2->y1 && + hospital1->y2 == hospital2->y2 && + hospital1->z == hospital1->z) { + return true; + } + + return false; +} + +void tickHandler(color_ostream& out, void* data) { + if ( !dwarfvet_enabled ) + return; + CoreSuspender suspend; + int32_t own_race_id = df::global::ui->race_id; + int32_t own_civ_id = df::global::ui->civ_id; + auto units = world->units.all; + + /** + * Generate a list of animal hospitals on the map + * + * Since activity zones can change any instant given user interaction + * we need to be constantly on the lookout for changed zones, and update + * our cached list on the fly if necessary. + **/ + + vector hospitals_on_map; + + // Because C++ iterators suck, we're going to build a temporary vector with the AHZ, and then + // copy it for my own bloody sanity (and compilance with the STL spec) + vector ahz_scratch; + + // Holding area for things to be added to the scratch + vector to_be_added; + + + // Walk the building tree, and generate a list of animal hospitals on the map + for (size_t b =0 ; b < world->buildings.all.size(); b++) { + df::building* building = world->buildings.all[b]; + if (isActiveAnimalHospital(building)) { + hospitals_on_map.push_back(building); + } + } + + int count_of_hospitals = hospitals_on_map.size(); + int hospitals_cached = animal_hospital_zones.size(); + //out.print ("count_of_Hospitals: %d, hospitals_cached: %d\n", count_of_hospitals, hospitals_cached); + // It's possible our hospital cache is empty, if so, simply copy it, and jump to the main logic + if (!hospitals_cached && count_of_hospitals) { + out.print("Populating hospital cache:\n"); + for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { + AnimalHospital * hospital = new AnimalHospital(*current_hospital, out); + out.print(" Found animal hospital %d at x1: %d, y1: %d from valid hospital list\n", + hospital->getID(), + (*current_hospital)->x1, + (*current_hospital)->y1, + (*current_hospital)->z + ); + animal_hospital_zones.push_back(hospital); + } + + goto processUnits; + } + + if (!count_of_hospitals && !hospitals_cached) { + // No hospitals found, delete any cache, and return + delete_animal_hospital_vector(out); + out.print("No hospitals found, plugin sleeping ...\n"); + goto cleanup; + } + + // Now walk our list of known hospitals, do a bit of checking, then compare + // TODO: this doesn't handle zone resizes at all + + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + // If a zone is changed at all, DF seems to reallocate it. + // + // Each AnimalHospital has a "to_be_deleted" bool. We're going to set that to true, and clear it if we can't + // find a matching hospital. This limits the number of times we need to walk through the AHZ list to twice, and + // lets us cleanly report it later + // + // Surviving hospitals will be copied to scratch which will become the new AHZ vector + + (*animal_hospital)->to_be_deleted = true; + for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { + + /* Keep the hospital if its still valid */ + if ((*animal_hospital)->getID() == (*current_hospital)->id) { + ahz_scratch.push_back(*animal_hospital); + (*animal_hospital)->to_be_deleted = false; + break; + } + + } + } + + // Report what we're deleting by checking the to_be_deleted bool. + // + // Whatsever left is added to the pending add list + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + if ((*animal_hospital)->to_be_deleted) { + out.print("Hospital #%d removed\n", (*animal_hospital)->getID()); + delete *animal_hospital; + } + } + + /* Now we need to walk the scratch and add anything that is a hospital and wasn't in the vector */ + + for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { + bool new_hospital = true; + + for (vector::iterator animal_hospital = ahz_scratch.begin(); animal_hospital != ahz_scratch.end(); animal_hospital++) { + if ((*animal_hospital)->getID() == (*current_hospital)->id) { + // Next if we're already here + new_hospital = false; + break; + } + } + + // Add it if its new + if (new_hospital == true) to_be_added.push_back(*current_hospital); + } + + /* Now add it to the scratch AHZ */ + for (vector::iterator current_hospital = to_be_added.begin(); current_hospital != to_be_added.end(); current_hospital++) { + // Add it to the vector + out.print("Adding new hospital #id at x1 %d y1: %d z: %d\n", + (*current_hospital)->id, + (*current_hospital)->x1, + (*current_hospital)->y1, + (*current_hospital)->z + ); + AnimalHospital * hospital = new AnimalHospital(*current_hospital, out); + ahz_scratch.push_back(hospital); + } + + /* Copy the scratch to the AHZ */ + animal_hospital_zones = ahz_scratch; + + // We always recheck the cache instead of counts because someone might have removed then added a hospital +/* if (hospitals_cached != count_of_hospitals) { + out.print("Hospitals on the map changed, rebuilding cache\n"); + + for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { + bool add_hospital = true; + + for (vector::iterator map_hospital = animal_hospital_zones.begin(); map_hospital != animal_hospital_zones.end(); map_hospital++) { + if (compareAnimalHospitalZones(*map_hospital, *current_hospital)) { + // Same hospital, we're good + add_hospital = false; + break; + } + } + + // Add it to the list + if (add_hospital) { + out.print("Adding zone at x1: %d, y1: %d to valid hospital list\n", (*current_hospital)->x1, (*current_hospital)->y1); + animal_hospital_zones.push_back(*current_hospital); + } + } + } +*/ +processUnits: + /* Code borrowed from petcapRemover.cpp */ + for ( size_t a = 0; a < units.size(); a++ ) { + df::unit* unit = units[a]; + + /* As hilarious as it would be, lets not treat FB :) */ + if ( unit->flags1.bits.dead || unit->flags1.bits.active_invader || unit->flags2.bits.underworld || unit->flags2.bits.visitor_uninvited || unit->flags2.bits.visitor ) { + continue; + } + + if ( !Units::isTamable(unit)) { + continue; + } + + /** + * So, for a unit to be elligable for the hospital, all the following must be true + * + * 1. It must be a member of our civilization + * 2. It must be tame (semi-wild counts for this) + * 2.1 If its not a dwarf, AND untame clear its civ out so traps work + * 3. It must have a health struct (which is generated by combat) + * 4. health->needs_healthcare must be set to true + * 5. If health->requires_recovery is set, the creature can't move under its own power + * and a Recover Wounded or Pen/Pasture job MUST be created by hand - TODO + * 6. An open spot in the "Animal Hospital" (activity zone with hospital+animal training set) + * must be available + * + * I apologize if this excessively verbose, but the healthcare system is stupidly conplex + * and there's tons of edgecases to watch out for, and I want someone else to ACTUALLY + * beside me able to understand what's going on + */ + + // 1. Make sure its our own civ + if (!Units::isOwnCiv(unit)) { + continue; + } + + // 2. Check for tameness + if (unit->training_level == df::animal_training_level::WildUntamed) { + // We don't IMMEDIATELY continue here, if the unit is + // part of our civ, it indiciates it WAS tamed, and reverted + // from SemiWild. Clear its civ flag so it looses TRAPAVOID + // + // Unfortunately, dwarves (or whatever is CIV_SELECTABLE) + // also have a default taming level of WildUntamed so + // check for this case + // + // Furthermore, it MIGHT be a werebeast, so check THAT too + // and exclude those as well. + // + // Finally, this breaks makeown. I might need to write a patch + // to set the tameness of "makeowned" units so dwarfvet can notice + // it + + if (unit->race == own_race_id || unit->enemy.normal_race == own_race_id) { + continue; + } else { + unit->civ_id = -1; + out.print ("Clearing civ on unit: %d", unit->id); + } + } + + // 3. Check for health struct + if (!unit->health) { + // Unit has not been injured ever; health struct MIA + continue; + } + + // 4. Check the healthcare flags + if (unit->health->flags.bits.needs_healthcare) { + /** + * So, for dwarves to care for a unit it must be resting in + * in a hospital zone. Since non-dwarves never take jobs + * this why animal healthcare doesn't work for animals despite + * animal caretaker being coded in DF itself + * + * How a unit gets there is dependent on several factors. If + * a unit can move under its own power, it will take the rest + * job, with a position of a bed in the hospital, then move + * into that bed and fall asleep. This triggers a doctor to + * treat the unit. + * + * If a unit *can't* move, it will set needs_recovery, which + * creates a "Recover Wounded" job in the job list, and then + * create the "Rest" job as listed above. Another dwarf with + * the right labors will go recover the unit, then the above + * logic kicks off. + * + * The necessary flags seem to be properly set for all units + * on the map, so in theory, we just need to make the jobs and + * we're in business, but from a realism POV, I don't think + * non-sentient animals would be smart enough to go to the + * hospital on their own, so instead, we're going to do the following + * + * If a unit CAN_THINK, and can move let it act like a dwarf, + * it will try and find an open spot in the hospital, and if so, + * go there to be treated. In vanilla, the only tamable animal + * with CAN_THINK are Gremlins, so this is actually an edge case + * but its the easiest to code. + * + * TODO: figure out exact logic for non-thinking critters. + */ + + // Now we need to find if this unit can be accepted at a hospital + bool awareOfUnit = false; + for (vector::iterator it = tracked_units.begin(); it != tracked_units.end(); it++) { + if ((*it) == unit->id) { + awareOfUnit = true; + } + } + // New unit for dwarfvet to be aware of! + if (!awareOfUnit) { + // The master list handles all patients which are accepted + // Check if this is a unit we're already aware of + + bool patient_accepted = false; + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end();) { + if ((*animal_hospital)->acceptPatient(unit->id, out)) { + out.print("Accepted patient %d at hospital %d\n", unit->id, (*animal_hospital)->getID()); + patient_accepted = true; + tracked_units.push_back(unit->id); + break; + } + + + } + } + } + } + + // The final step, process all patients! + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + (*animal_hospital)->calculateHospital(true, out); + (*animal_hospital)->processPatients(out); + } + +cleanup: + EventManager::unregisterAll(plugin_self); + EventManager::EventHandler handle(tickHandler, howOften); + EventManager::registerTick(handle, howOften, plugin_self); +} + +command_result dwarfvet (color_ostream &out, std::vector & parameters) +{ + CoreSuspender suspend; + + for ( size_t a = 0; a < parameters.size(); a++ ) { + if ( parameters[a] == "enable" ) { + out.print("dwarfvet enabled!\n"); + dwarfvet_enabled = true; + } + if ( parameters[a] == "disable") { + out.print("dwarvet disabled!\n"); + dwarfvet_enabled = false; + } + if ( parameters[a] == "report") { + out.print("Current animal hospitals are:\n"); + for (size_t b =0 ; b < world->buildings.all.size(); b++) { + df::building* building = world->buildings.all[b]; + if (isActiveAnimalHospital(building)) { + out.print(" at x1: %d, x2%: %d, y1: %d, y2: %d, z: %d\n", building->x1, building->x2, building->y1, building->y2, building->z); + } + } + return CR_OK; + } + if ( parameters[a] == "report-usage") { + out.print("Current animal hospitals are:\n"); + for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + (*animal_hospital)->calculateHospital(true, out); + (*animal_hospital)->reportUsage(out); + } + return CR_OK; + } + } + + if ( !dwarfvet_enabled ) { + return CR_OK; + } + + EventManager::unregisterAll(plugin_self); + EventManager::EventHandler handle(tickHandler, howOften); + EventManager::registerTick(handle, howOften, plugin_self); + + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + if (enable && !dwarfvet_enabled) { + dwarfvet_enabled = true; + } + else if (!enable && dwarfvet_enabled) { + delete_animal_hospital_vector(out); + dwarfvet_enabled = false; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) + { + case DFHack::SC_MAP_LOADED: + break; + case DFHack::SC_MAP_UNLOADED: + delete_animal_hospital_vector(out); + dwarfvet_enabled = false; + break; + default: + break; + } + return CR_OK; +} \ No newline at end of file