|
|
@ -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> ¶meters);
|
|
|
|
// 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 {
|
|
|
|
|
|
|
|
DEBUG(status,out).print("%s from the API, but already %s; no action\n",
|
|
|
|
|
|
|
|
is_enabled ? "enabled" : "disabled",
|
|
|
|
|
|
|
|
is_enabled ? "enabled" : "disabled");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void AnimalHospital::processPatients(color_ostream &out) {
|
|
|
|
DFhackCExport command_result plugin_load_data(color_ostream &out) {
|
|
|
|
// Where the magic happens
|
|
|
|
cycle_timestamp = 0;
|
|
|
|
for (Patient *patient : this->accepted_patients) {
|
|
|
|
config = World::GetPersistentData(CONFIG_KEY);
|
|
|
|
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 (!config.isValid()) {
|
|
|
|
if (!real_unit || !Units::isActive(real_unit) || !real_unit->health->flags.bits.needs_healthcare) {
|
|
|
|
DEBUG(status,out).print("no config found in this save; initializing\n");
|
|
|
|
// discharge the patient from the hospital
|
|
|
|
config = World::AddPersistentData(CONFIG_KEY);
|
|
|
|
this->dischargePatient(patient, out);
|
|
|
|
set_config_bool(CONFIG_IS_ENABLED, is_enabled);
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Give the unit a job if they don't have any
|
|
|
|
is_enabled = get_config_bool(CONFIG_IS_ENABLED);
|
|
|
|
if (!real_unit->job.current_job) {
|
|
|
|
DEBUG(status,out).print("loading persisted enabled state: %s\n",
|
|
|
|
// Create REST struct
|
|
|
|
is_enabled ? "true" : "false");
|
|
|
|
df::job * job = new df::job;
|
|
|
|
return CR_OK;
|
|
|
|
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);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
|
|
|
|
static vector<AnimalHospital*> animal_hospital_zones;
|
|
|
|
if (event == DFHack::SC_WORLD_UNLOADED) {
|
|
|
|
|
|
|
|
if (is_enabled) {
|
|
|
|
void delete_animal_hospital_vector(color_ostream &out) {
|
|
|
|
DEBUG(status,out).print("world unloaded; disabling %s\n",
|
|
|
|
if (dwarfvet_enabled) {
|
|
|
|
plugin_name);
|
|
|
|
out.print("Clearing all animal hospitals\n");
|
|
|
|
is_enabled = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
|
|
|
DFhackCExport command_result plugin_onupdate(color_ostream &out) {
|
|
|
|
{
|
|
|
|
if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS)
|
|
|
|
|
|
|
|
dwarfvet_cycle(out);
|
|
|
|
return CR_OK;
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool isActiveAnimalHospital(df::building * building) {
|
|
|
|
static bool call_dwarfvet_lua(color_ostream *out, const char *fn_name,
|
|
|
|
if (Buildings::isHospital(building) && Buildings::isAnimalTraining(building) && Buildings::isActive(building)) {
|
|
|
|
int nargs = 0, int nres = 0,
|
|
|
|
return true;
|
|
|
|
Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA,
|
|
|
|
}
|
|
|
|
Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) {
|
|
|
|
|
|
|
|
DEBUG(status).print("calling dwarfvet lua function: '%s'\n", fn_name);
|
|
|
|
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 == hospital2->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::plotinfo->race_id;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 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
|
|
|
|
CoreSuspender guard;
|
|
|
|
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 */
|
|
|
|
auto L = Lua::Core::State;
|
|
|
|
if (animal_hospital->getID() == current_hospital->id) {
|
|
|
|
Lua::StackUnwinder top(L);
|
|
|
|
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.
|
|
|
|
if (!out)
|
|
|
|
//
|
|
|
|
out = &Core::getInstance().getConsole();
|
|
|
|
// 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 */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (df::building *current_hospital : hospitals_on_map) {
|
|
|
|
|
|
|
|
bool new_hospital = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
return Lua::CallLuaModuleFunction(*out, L, "plugins.dwarfvet", fn_name,
|
|
|
|
if (new_hospital == true) to_be_added.push_back(current_hospital);
|
|
|
|
nargs, nres,
|
|
|
|
}
|
|
|
|
std::forward<Lua::LuaLambda&&>(args_lambda),
|
|
|
|
|
|
|
|
std::forward<Lua::LuaLambda&&>(res_lambda));
|
|
|
|
/* 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 */
|
|
|
|
|
|
|
|
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 (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> ¶meters) {
|
|
|
|
{
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|