Merge remote-tracking branch 'NCommander/dwarfvet' into develop

develop
lethosor 2016-07-29 16:25:53 -04:00
commit 79377669a1
7 changed files with 879 additions and 1 deletions

@ -37,6 +37,10 @@ Lua
---
- Label widgets can now easily register handlers for mouse clicks
New Plugins
-----------
- `dwarfvet` enables animal caretaking.
New Features
------------
- `add-thought`: allow syndrome name as ``-thought`` argument

@ -57,6 +57,7 @@ Max maxthyme Max^TM
melkor217 melkor217
Meneth32
Meph
Michael Casadevall NCommander
Michon van Dooren MaienM
miffedmap miffedmap
Mike Stewart thewonderidiot

@ -1109,7 +1109,6 @@ Example:
This will confiscate rotten and dropped food, garbage on the floors and any
worn items with 'X' damage and above.
.. _dwarfmonitor:
dwarfmonitor
@ -1187,6 +1186,28 @@ Some widgets support additional options:
displayed as ``-1`` when the cursor is outside of the DF window; otherwise,
nothing will be displayed.
.. _dwarfvet:
dwarfvet
============
Enables Animal Caretaker functionality
Always annoyed your dragons become useless after a minor injury? Well, with
dwarfvet, your animals become first rate members of your fort. It can also
be used to train medical skills.
Animals need to be treated in an animal hospital, which is simply a hospital
that is also an animal training zone. The console will print out a list on game
load, and whenever one is added or removed. Dwarfs must have the Animal Caretaker
labor to treat animals. Normal medical skills are used (and no experience is given
to the Animal Caretaker skill).
Options:
:enable: Enables Animal Caretakers to treat and manage animals
:disable: Turns off the plguin
:report: Reports all zones that the game considers animal hospitals
.. _workNow:
workNow

@ -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);
}

@ -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)
{

@ -92,6 +92,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)

@ -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 <map>
#include <vector>
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<int32_t> 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<bool> spots_in_use;
vector<int32_t> building_in_hospital_notification; /* If present, we already notified about this */
vector<Patient*> 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<Patient*>::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<bool>::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<bool>::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<Patient*>::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<int32_t>::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<Patient*>::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<df::general_ref_unit_workerst>();
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<AnimalHospital*> animal_hospital_zones;
void delete_animal_hospital_vector(color_ostream &out) {
out.print("Clearing all animal hospitals\n");
for (vector<AnimalHospital*>::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 <std::string> & parameters);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &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<df::building*> 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<AnimalHospital*> ahz_scratch;
// Holding area for things to be added to the scratch
vector<df::building*> 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<df::building*>::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<AnimalHospital*>::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<df::building*>::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<AnimalHospital*>::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<df::building*>::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) {
bool new_hospital = true;
for (vector<AnimalHospital*>::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<df::building*>::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<df::building*>::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) {
bool add_hospital = true;
for (vector<AnimalHospital*>::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<int32_t>::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<AnimalHospital*>::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<AnimalHospital*>::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 <std::string> & 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<AnimalHospital*>::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;
}