@ -20,735 +20,170 @@
* THE SOFTWARE .
* THE SOFTWARE .
* */
* */
# include "Console.h"
# include "Debug.h"
# include "Core.h"
# include "LuaTools.h"
# include "DataDefs.h"
# include "Export.h"
# include "PluginManager.h"
# include "PluginManager.h"
# include "modules/EventManager.h"
# include "modules/Persistence.h"
# include "modules/Units.h"
# include "modules/Units.h"
# include "modules/Buildings.h"
# include "modules/World.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/plotinfost.h"
# include "df/unit.h"
# include "df/unit_health_info.h"
# include "df/unit_health_flags.h"
# include "df/world.h"
# include "df/world.h"
# include <map>
# include <unordered_set>
# include <vector>
using namespace DFHack ;
using namespace DFHack ;
using namespace DFHack : : Units ;
using std : : string ;
using namespace DFHack : : Buildings ;
using std : : vector ;
using namespace std ;
DFHACK_PLUGIN ( " dwarfvet " ) ;
DFHACK_PLUGIN ( " dwarfvet " ) ;
DFHACK_PLUGIN_IS_ENABLED ( dwarfvet _enabled) ;
DFHACK_PLUGIN_IS_ENABLED ( is_enabled ) ;
REQUIRE_GLOBAL ( plotinfo ) ;
REQUIRE_GLOBAL ( world ) ;
REQUIRE_GLOBAL ( world ) ;
static unordered_set < int32_t > tracked_units ;
namespace DFHack {
static int32_t howOften = 100 ;
// for configuration-related logging
DBG_DECLARE ( dwarfvet , status , DebugCategory : : LINFO ) ;
struct hospital_spot {
// for logging during the periodic scan
int32_t x ;
DBG_DECLARE ( dwarfvet , cycle , DebugCategory : : LINFO ) ;
int32_t y ;
int32_t z ;
} ;
class Patient {
public :
// Constructor/Deconstrctor
Patient ( int32_t id , size_t spot_index , int32_t x , int32_t y , int32_t z ) ;
int32_t getID ( ) { return this - > id ; } ;
size_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 ;
int32_t id ;
size_t spot_index ;
} ;
Patient : : Patient ( int32_t id , size_t spot_index , int32_t x , int32_t y , int32_t z ) {
this - > id = id ;
this - > spot_index = spot_index ;
this - > spot_in_hospital . x = x ;
this - > spot_in_hospital . y = y ;
this - > spot_in_hospital . z = z ;
}
}
class AnimalHospital {
static const string CONFIG_KEY = string ( plugin_name ) + " /config " ;
static PersistentDataItem config ;
public :
enum ConfigValues {
// Constructor
CONFIG_IS_ENABLED = 0 ,
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 ;
} ;
} ;
static int get_config_val ( int index ) {
AnimalHospital : : AnimalHospital ( df : : building * building , color_ostream & out ) {
if ( ! config . isValid ( ) )
// Copy in what we need to know
return - 1 ;
id = building - > id ;
return config . ival ( index ) ;
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 + 1 ;
this - > height = y2 - y1 + 1 ;
// And calculate the hospital!
this - > calculateHospital ( true , out ) ;
}
}
static bool get_config_bool ( int index ) {
AnimalHospital : : ~ AnimalHospital ( ) {
return get_config_val ( index ) = = 1 ;
// Go through and delete all the patients
for ( Patient * accepted_patient : this - > accepted_patients ) {
delete accepted_patient ;
}
}
}
static void set_config_val ( int index , int value ) {
bool AnimalHospital : : acceptPatient ( int32_t id , color_ostream & out ) {
if ( config . isValid ( ) )
// This function determines if we can accept a patient, and if we will.
config . ival ( index ) = value ;
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 ;
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 ;
}
}
static void set_config_bool ( int index , bool value ) {
// Before any use of the hospital, we need to make calculate open spots
set_config_val ( index , value ? 1 : 0 ) ;
// 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 ( bool spot : this - > spots_in_use ) {
if ( spot ) out . print ( " X " ) ;
else out . print ( " - " ) ;
length_cursor - - ;
if ( length_cursor < = 0 ) {
out . print ( " \n " ) ;
length_cursor = this - > length ;
}
}
out . print ( " \n " ) ;
}
}
void AnimalHospital : : calculateHospital ( bool force , color_ostream & out ) {
static const int32_t CYCLE_TICKS = 2459 ; // a prime number that's around 2 days
// Only calculate out the hospital if we actually have a patient in it
static int32_t cycle_timestamp = 0 ; // world->frame_counter at last cycle
// (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
static command_result do_command ( color_ostream & out , vector < string > & parameters ) ;
// This can change if a hospital has been resized
static void dwarfvet_cycle ( color_ostream & out ) ;
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 ( df : : building * building : world - > buildings . all ) {
// 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 overlap_x1 = std : : max ( building - > x1 , this - > x1 ) ;
int overlap_y1 = std : : max ( building - > y1 , this - > y1 ) ;
int overlap_x2 = std : : min ( building - > x2 , this - > x2 ) ;
int overlap_y2 = std : : min ( building - > y2 , this - > y2 ) ;
for ( int x = overlap_x1 ; x < = overlap_x2 ; x + + ) {
for ( int y = overlap_y1 ; y < = overlap_y2 ; y + + ) {
int spot_index = ( x - this - > x1 ) + ( this - > length * ( y - this - > y1 ) ) ;
spots_in_use . at ( spot_index ) = true ;
}
}
}
DFhackCExport command_result plugin_init ( color_ostream & out , vector < PluginCommand > & commands ) {
commands . push_back ( PluginCommand (
plugin_name ,
" Allow animals to be treated at hospitals. " ,
do_command ) ) ;
return CR_OK ;
}
}
// Self explanatory
DFhackCExport command_result plugin_enable ( color_ostream & out , bool enable ) {
void AnimalHospital : : dischargePatient ( Patient * patient , color_ostream & out ) {
if ( ! Core : : getInstance ( ) . isWorldLoaded ( ) ) {
int32_t id = patient - > getID ( ) ;
out . printerr ( " Cannot enable %s without a loaded world. \n " , plugin_name ) ;
return CR_FAILURE ;
// 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 . at ( ( * accepted_patient ) - > getSpotIndex ( ) ) = false ;
this - > spots_open + + ;
delete ( * accepted_patient ) ;
this - > accepted_patients . erase ( accepted_patient ) ;
break ;
}
}
}
// And the master list
if ( enable ! = is_enabled ) {
tracked_units . erase ( id ) ;
is_enabled = enable ;
DEBUG ( status , out ) . print ( " %s from the API; persisting \n " ,
return ;
is_enabled ? " enabled " : " disabled " ) ;
}
set_config_bool ( CONFIG_IS_ENABLED , is_enabled ) ;
} else {
void AnimalHospital : : processPatients ( color_ostream & out ) {
DEBUG ( status , out ) . print ( " %s from the API, but already %s; no action \n " ,
// Where the magic happens
is_enabled ? " enabled " : " disabled " ,
for ( Patient * patient : this - > accepted_patients ) {
is_enabled ? " enabled " : " disabled " ) ;
int id = patient - > getID ( ) ;
df : : unit * real_unit = df : : unit : : find ( id ) ;
// Check to make sure the unit hasn't expired before assigning a job, or if they've been healed
if ( ! real_unit | | ! Units : : isActive ( real_unit ) | | ! 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 ) ;
}
}
}
return CR_OK ;
}
}
DFhackCExport command_result plugin_load_data ( color_ostream & out ) {
cycle_timestamp = 0 ;
config = World : : GetPersistentData ( CONFIG_KEY ) ;
static vector < AnimalHospital * > animal_hospital_zones ;
if ( ! config . isValid ( ) ) {
DEBUG ( status , out ) . print ( " no config found in this save; initializing \n " ) ;
void delete_animal_hospital_vector ( color_ostream & out ) {
config = World : : AddPersistentData ( CONFIG_KEY ) ;
if ( dwarfvet_enabled ) {
set_config_bool ( CONFIG_IS_ENABLED , is_enabled ) ;
out . print ( " Clearing all animal hospitals \n " ) ;
}
for ( AnimalHospital * animal_hospital : animal_hospital_zones ) {
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 treated at animal hospitals " ,
dwarfvet ) ) ;
return CR_OK ;
}
DFhackCExport command_result plugin_shutdown ( color_ostream & out )
is_enabled = get_config_bool ( CONFIG_IS_ENABLED ) ;
{
DEBUG ( status , out ) . print ( " loading persisted enabled state: %s \n " ,
is_enabled ? " true " : " false " ) ;
return CR_OK ;
return CR_OK ;
}
}
bool isActiveAnimalHospital ( df : : building * building ) {
DFhackCExport command_result plugin_onstatechange ( color_ostream & out , state_change_event event ) {
if ( Buildings : : isHospital ( building ) & & Buildings : : isAnimalTraining ( building ) & & Buildings : : isActive ( building ) ) {
if ( event = = DFHack : : SC_WORLD_UNLOADED ) {
return true ;
if ( is_enabled ) {
DEBUG ( status , out ) . print ( " world unloaded; disabling %s \n " ,
plugin_name ) ;
is_enabled = false ;
}
}
}
return CR_OK ;
return false ;
}
}
bool compareAnimalHospitalZones ( df : : building * hospital1 , df : : building * hospital2 ) {
DFhackCExport command_result plugin_onupdate ( color_ostream & out ) {
// We compare hospitals by checking if positions are identical, not by ID
if ( is_enabled & & world - > frame_counter - cycle_timestamp > = CYCLE_TICKS )
// since activity zones can easily be changed in size
dwarfvet_cycle ( out ) ;
return CR_OK ;
if ( hospital1 - > x1 = = hospital2 - > x1 & &
hospital1 - > x2 = = hospital2 - > x2 & &
hospital1 - > y1 = = hospital2 - > y1 & &
hospital1 - > y2 = = hospital2 - > y2 & &
hospital1 - > z = = hospital2 - > z ) {
return true ;
}
return false ;
}
}
void tickHandler ( color_ostream & out , void * data ) {
static bool call_dwarfvet_lua ( color_ostream * out , const char * fn_name ,
if ( ! dwarfvet_enabled )
int nargs = 0 , int nres = 0 ,
return ;
Lua : : LuaLambda & & args_lambda = Lua : : DEFAULT_LUA_LAMBDA ,
CoreSuspender suspend ;
Lua : : LuaLambda & & res_lambda = Lua : : DEFAULT_LUA_LAMBDA ) {
int32_t own_race_id = df : : global : : plotinfo - > race_id ;
DEBUG ( status ) . print ( " calling dwarfvet lua function: '%s' \n " , fn_name ) ;
/**
* 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 ( df : : building * building : df : : building : : get_vector ( ) ) {
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 ( df : : building * current_hospital : hospitals_on_map ) {
AnimalHospital * hospital = new AnimalHospital ( current_hospital , out ) ;
out . print ( " Found animal hospital %d at x1: %d, y1: %d, z: %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, cache is empty, just return
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 ( AnimalHospital * animal_hospital : animal_hospital_zones ) {
// 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 ( df : : building * current_hospital : hospitals_on_map ) {
/* 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 ( AnimalHospital * animal_hospital : animal_hospital_zones ) {
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 */
CoreSuspender guard ;
for ( df : : building * current_hospital : hospitals_on_map ) {
auto L = Lua : : Core : : State ;
bool new_hospital = true ;
Lua : : StackUnwinder top ( L ) ;
for ( AnimalHospital * animal_hospital : ahz_scratch ) {
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 ( df : : building * current_hospital : to_be_added ) {
// Add it to the vector
out . print ( " Adding new hospital #id: %d 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 */
if ( ! out )
animal_hospital_zones = ahz_scratch ;
out = & Core : : getInstance ( ) . getConsole ( ) ;
// We always recheck the cache instead of counts because someone might have removed then added a hospital
return Lua : : CallLuaModuleFunction ( * out , L , " plugins.dwarfvet " , fn_name ,
/* if (hospitals_cached != count_of_hospitals) {
nargs , nres ,
out . print ( " Hospitals on the map changed, rebuilding cache \n " ) ;
std : : forward < Lua : : LuaLambda & & > ( args_lambda ) ,
std : : forward < Lua : : LuaLambda & & > ( res_lambda ) ) ;
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 ( df : : unit * unit : df : : unit : : get_vector ( ) ) {
/* As hilarious as it would be, lets not treat FB :) */
if ( ! Units : : isActive ( unit ) | | 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 = tracked_units . count ( unit - > id ) ;
// 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
for ( auto animal_hospital : animal_hospital_zones ) {
if ( animal_hospital - > acceptPatient ( unit - > id , out ) ) {
out . print ( " Accepted patient %d at hospital %d \n " , unit - > id , animal_hospital - > getID ( ) ) ;
tracked_units . insert ( unit - > id ) ;
break ;
}
}
}
}
}
// The final step, process all patients!
for ( AnimalHospital * animal_hospital : animal_hospital_zones ) {
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 )
static command_result do_command ( color_ostream & out , vector < string > & parameters ) {
{
CoreSuspender suspend ;
CoreSuspender suspend ;
for ( size_t a = 0 ; a < parameters . size ( ) ; a + + ) {
if ( ! Core : : getInstance ( ) . isWorldLoaded ( ) ) {
if ( parameters [ a ] = = " enable " ) {
out . printerr ( " Cannot run %s without a loaded world. \n " , plugin_name ) ;
out . print ( " dwarfvet enabled! \n " ) ;
return CR_FAILURE ;
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 ( df : : building * building : df : : building : : get_vector ( ) ) {
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 ( AnimalHospital * animal_hospital : animal_hospital_zones ) {
animal_hospital - > calculateHospital ( true , out ) ;
animal_hospital - > reportUsage ( out ) ;
}
return CR_OK ;
}
}
}
if ( ! dwarfvet_enabled ) {
bool show_help = false ;
return CR_OK ;
if ( ! call_dwarfvet_lua ( & out , " parse_commandline " , 1 , 1 ,
[ & ] ( lua_State * L ) {
Lua : : PushVector ( L , parameters ) ;
} ,
[ & ] ( lua_State * L ) {
show_help = ! lua_toboolean ( L , - 1 ) ;
} ) ) {
return CR_FAILURE ;
}
}
EventManager : : unregisterAll ( plugin_self ) ;
return show_help ? CR_WRONG_USAGE : CR_OK ;
EventManager : : EventHandler handle ( tickHandler , howOften ) ;
EventManager : : registerTick ( handle , howOften , plugin_self ) ;
return CR_OK ;
}
}
DFhackCExport command_result plugin_enable ( color_ostream & out , bool enable )
static void dwarfvet_cycle ( color_ostream & out ) {
{
// mark that we have recently run
if ( enable & & ! dwarfvet_enabled ) {
cycle_timestamp = world - > frame_counter ;
dwarfvet_enabled = true ;
}
else if ( ! enable & & dwarfvet_enabled ) {
delete_animal_hospital_vector ( out ) ;
dwarfvet_enabled = false ;
}
return CR_OK ;
DEBUG ( cycle , out ) . print ( " running %s cycle \n " , plugin_name ) ;
call_dwarfvet_lua ( & out , " checkup " ) ;
}
}
DFhackCExport command_result plugin_onstatechange ( color_ostream & out , state_change_event event )
DFHACK_PLUGIN_LUA_FUNCTIONS {
{
DFHACK_LUA_FUNCTION ( dwarfvet_cycle ) ,
switch ( event )
DFHACK_LUA_END
{
} ;
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 ;
}