/plugins/zone.cpp
C++ | 3548 lines | 3015 code | 314 blank | 219 comment | 883 complexity | 7628d49699e466c2871b2d4bd2c748f4 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
Large files files are truncated, but you can click here to view the full file
- // Intention: help with activity zone management (auto-pasture animals, auto-pit goblins, ...)
- //
- // the following things would be nice:
- // - dump info about pastures, pastured animals, count non-pastured tame animals, print gender info
- // - help finding caged dwarves? (maybe even allow to build their cages for fast release)
- // - dump info about caged goblins, animals, ...
- // - count grass tiles on pastures, move grazers to new pasture if old pasture is empty
- // move hungry unpastured grazers to pasture with grass
- //
- // What is working so far:
- // - print detailed info about activity zone and units under cursor (mostly for checking refs and stuff)
- // - mark a zone which is used for future assignment commands
- // - assign single selected creature to a zone
- // - mass-assign creatures using filters
- // - unassign single creature under cursor from current zone
- // - pitting own dwarves :)
- // - full automation of handling mini-pastures over nestboxes:
- // go through all pens, check if they are empty and placed over a nestbox
- // find female tame egg-layer who is not assigned to another pen and assign it to nestbox pasture
- // maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds
- // state and sleep setting is saved the first time autonestbox is started (to avoid writing stuff if the plugin is never used)
- // - full automation of marking live-stock for slaughtering
- // races can be added to a watchlist and it can be set how many male/female kids/adults are left alive
- // adding to the watchlist can be automated as well.
- // config for autobutcher (state and sleep setting) is saved the first time autobutcher is started
- // config for watchlist entries is saved when they are created or modified
- #include <iostream>
- #include <iomanip>
- #include <climits>
- #include <vector>
- #include <algorithm>
- #include <string>
- #include <sstream>
- #include <ctime>
- #include <cstdio>
- using namespace std;
- #include "Core.h"
- #include "Console.h"
- #include "Export.h"
- #include "PluginManager.h"
- #include "modules/Units.h"
- #include "modules/Maps.h"
- #include "modules/Gui.h"
- #include "modules/Materials.h"
- #include "modules/MapCache.h"
- #include "modules/Buildings.h"
- #include "modules/World.h"
- #include "MiscUtils.h"
- #include <df/ui.h>
- #include "df/world.h"
- #include "df/world_raws.h"
- #include "df/building_def.h"
- #include "df/building_civzonest.h"
- #include "df/building_cagest.h"
- #include "df/building_chainst.h"
- #include "df/building_nest_boxst.h"
- #include "df/general_ref_building_civzone_assignedst.h"
- #include <df/creature_raw.h>
- #include <df/caste_raw.h>
- using std::vector;
- using std::string;
- using namespace DFHack;
- using namespace df::enums;
- using df::global::world;
- using df::global::cursor;
- using df::global::ui;
- using namespace DFHack::Gui;
- command_result df_zone (color_ostream &out, vector <string> & parameters);
- command_result df_autonestbox (color_ostream &out, vector <string> & parameters);
- command_result df_autobutcher(color_ostream &out, vector <string> & parameters);
- DFHACK_PLUGIN("zone");
- const string zone_help =
- "Allows easier management of pens/pastures, pits and cages.\n"
- "Options:\n"
- " set - set zone under cursor as default for future assigns\n"
- " assign - assign creature(s) to a pen or pit\n"
- " if no filters are used, a single unit must be selected.\n"
- " can be followed by valid building id which will then be set.\n"
- " building must be a pen/pasture, pit or cage.\n"
- " slaughter - mark creature(s) for slaughter\n"
- " if no filters are used, a single unit must be selected.\n"
- " with filters named units are ignored unless specified.\n"
- " unassign - unassign selected creature(s) from it's zone or cage\n"
- " nick - give unit(s) nicknames (e.g. all units in a cage)\n"
- " remnick - remove nicknames\n"
- " tocages - assign to (multiple) built cages inside a pen/pasture\n"
- " spreads creatures evenly among cages for faster hauling.\n"
- " uinfo - print info about selected unit\n"
- " zinfo - print info about zone(s) under cursor\n"
- " verbose - print some more info, mostly useless debug stuff\n"
- " filters - print list of supported filters\n"
- " examples - print some usage examples\n"
- ;
- const string zone_help_filters =
- "Filters (to be used in combination with 'all' or 'count'):\n"
- "These filters can not be used with the prefix 'not':"
- " all - in combinations with zinfo/uinfo: print all zones/units\n"
- " in combination with assign: process all units\n"
- " should be used in combination with further filters\n"
- " count - must be followed by number. process X units\n"
- " should be used in combination with further filters\n"
- " unassigned - not assigned to zone, chain or built cage\n"
- " age - exact age. must be followed by number\n"
- " minage - minimum age. must be followed by number\n"
- " maxage - maximum age. must be followed by number\n"
- "These filters can be used with the prefix 'not' (e.g. 'not own'):"
- " race - must be followed by a race raw id (e.g. BIRD_TURKEY)\n"
- " caged - in a built cage\n"
- " own - from own civilization\n"
- " war - trained war creature\n"
- " tamed - tamed\n"
- " named - has name or nickname\n"
- " can be used to mark named units for slaughter\n"
- " merchant - is a merchant / belongs to a merchant\n"
- " can be used to pit merchants and slaughter their animals\n"
- " (could have weird effects during trading, be careful)\n"
- " trained - obvious\n"
- " trainablewar - can be trained for war (and is not already trained)\n"
- " trainablehunt- can be trained for hunting (and is not already trained)\n"
- " male - obvious\n"
- " female - obvious\n"
- " egglayer - race lays eggs (use together with 'female')\n"
- " grazer - is a grazer\n"
- " milkable - race is milkable (use together with 'female')\n"
- ;
- const string zone_help_examples =
- "Example for assigning single units:\n"
- " (ingame) move cursor to a pen/pasture or pit zone\n"
- " (dfhack) 'zone set' to use this zone for future assignments\n"
- " (dfhack) map 'zone assign' to a hotkey of your choice\n"
- " (ingame) select unit with 'v', 'k' or from unit list or inside a cage\n"
- " (ingame) press hotkey to assign unit to it's new home (or pit)\n"
- "Examples for assigning with filters:\n"
- " (this assumes you have already set up a target zone)\n"
- " zone assign all own grazer maxage 10\n"
- " zone assign all own milkable not grazer\n"
- " zone assign count 5 own female milkable\n"
- " zone assign all own race DWARF maxage 2\n"
- " throw all useless kids into a pit :)\n"
- "Notes:\n"
- " Unassigning per filters ignores built cages and chains currently. Usually you\n"
- " should always use the filter 'own' (which implies tame) unless you want to\n"
- " use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you\n"
- " specify 'race DWARF' and it ignores merchants and their animals unless you\n"
- " specify 'merchant' (so it's safe to use 'assign all own' to one big pasture\n"
- " if you want to have all your animals at the same place).\n"
- " 'egglayer' and 'milkable' should be used together with 'female'\n"
- " well, unless you have a mod with egg-laying male elves who give milk...\n";
- const string autonestbox_help =
- "Assigns unpastured female egg-layers to nestbox zones.\n"
- "Requires that you create pen/pasture zones above nestboxes.\n"
- "If the pen is bigger than 1x1 the nestbox must be in the top left corner.\n"
- "Only 1 unit will be assigned per pen, regardless of the size.\n"
- "The age of the units is currently not checked, most birds grow up quite fast.\n"
- "When called without options autonestbox will instantly run once.\n"
- "Options:\n"
- " start - run every X frames (df simulation ticks)\n"
- " default: X=6000 (~60 seconds at 100fps)\n"
- " stop - stop running automatically\n"
- " sleep X - change timer to sleep X frames between runs.\n";
- const string autobutcher_help =
- "Assigns your lifestock for slaughter once it reaches a specific count. Requires\n"
- "that you add the target race(s) to a watch list. Only tame units will be\n"
- "processed. Named units will be completely ignored (you can give animals\n"
- "nicknames with the tool 'rename unit' to protect them from getting slaughtered\n"
- "automatically. Trained war or hunting pets will be ignored.\n"
- "Once you have too much adults, the oldest will be butchered first.\n"
- "Once you have too much kids, the youngest will be butchered first.\n"
- "If you don't set a target count the following default will be used:\n"
- "1 male kid, 5 female kids, 1 male adult, 5 female adults.\n"
- "Options:\n"
- " start - run every X frames (df simulation ticks)\n"
- " default: X=6000 (~60 seconds at 100fps)\n"
- " stop - stop running automatically\n"
- " sleep X - change timer to sleep X frames between runs.\n"
- " watch R - start watching race(s)\n"
- " R = valid race RAW id (ALPACA, BIRD_TURKEY, etc)\n"
- " or a list of RAW ids seperated by spaces\n"
- " or the keyword 'all' which affects your whole current watchlist.\n"
- " unwatch R - stop watching race(s)\n"
- " the current target settings will be remembered\n"
- " forget R - unwatch race(s) and forget target settings for it/them\n"
- " autowatch - automatically adds all new races (animals you buy\n"
- " from merchants, tame yourself or get from migrants)\n"
- " to the watch list using default target count\n"
- " noautowatch - stop auto-adding new races to the watch list\n"
- " list - print status and watchlist\n"
- " list_export - print status and watchlist in batchfile format\n"
- " can be used to copy settings into another savegame\n"
- " usage: 'dfhack-run autobutcher list_export > xyz.bat' \n"
- " target fk mk fa ma R\n"
- " - set target count for specified race:\n"
- " fk = number of female kids\n"
- " mk = number of male kids\n"
- " fa = number of female adults\n"
- " ma = number of female adults\n"
- " R = 'all' sets count for all races on the current watchlist\n"
- " including the races which are currenly set to 'unwatched'\n"
- " and sets the new default for future watch commands\n"
- " R = 'new' sets the new default for future watch commands\n"
- " without changing your current watchlist\n"
- " example - print some usage examples\n";
- const string autobutcher_help_example =
- "Examples:\n"
- " autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY\n"
- " autobutcher watch ALPACA BIRD_TURKEY\n"
- " autobutcher start\n"
- " This means you want to have max 7 kids (4 female, 3 male) and max 3 adults\n"
- " (2 female, 1 male) of the races alpaca and turkey. Once the kids grow up the\n"
- " oldest adults will get slaughtered. Excess kids will get slaughtered starting\n"
- " the the youngest to allow that the older ones grow into adults.\n"
- " autobutcher target 0 0 0 0 new\n"
- " autobutcher autowatch\n"
- " autobutcher start\n"
- " This tells autobutcher to automatically put all new races onto the watchlist\n"
- " and mark unnamed tame units for slaughter as soon as they arrive in your\n"
- " fortress. Settings already made for some races will be left untouched.\n";
- command_result init_autobutcher(color_ostream &out);
- command_result cleanup_autobutcher(color_ostream &out);
- command_result start_autobutcher(color_ostream &out);
- command_result init_autonestbox(color_ostream &out);
- command_result cleanup_autonestbox(color_ostream &out);
- command_result start_autonestbox(color_ostream &out);
- DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
- {
- commands.push_back(PluginCommand(
- "zone", "manage activity zones.",
- df_zone, false,
- zone_help.c_str()
- ));
- commands.push_back(PluginCommand(
- "autonestbox", "auto-assign nestbox zones.",
- df_autonestbox, false,
- autonestbox_help.c_str()
- ));
- commands.push_back(PluginCommand(
- "autobutcher", "auto-assign lifestock for butchering.",
- df_autobutcher, false,
- autobutcher_help.c_str()
- ));
- init_autobutcher(out);
- init_autonestbox(out);
- return CR_OK;
- }
- DFhackCExport command_result plugin_shutdown ( color_ostream &out )
- {
- cleanup_autobutcher(out);
- cleanup_autonestbox(out);
- return CR_OK;
- }
- ///////////////
- // stuff for autonestbox and autobutcher
- // should be moved to own plugin once the tool methods it shares with the zone plugin are moved to Unit.h / Building.h
- command_result autoNestbox( color_ostream &out, bool verbose );
- command_result autoButcher( color_ostream &out, bool verbose );
- static bool enable_autonestbox = false;
- static bool enable_autobutcher = false;
- static bool enable_autobutcher_autowatch = false;
- static size_t sleep_autonestbox = 6000;
- static size_t sleep_autobutcher = 6000;
- static bool autonestbox_did_complain = false; // avoids message spam
- static PersistentDataItem config_autobutcher;
- static PersistentDataItem config_autonestbox;
- DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
- {
- switch (event)
- {
- case DFHack::SC_MAP_LOADED:
- // initialize from the world just loaded
- init_autobutcher(out);
- init_autonestbox(out);
- break;
- case DFHack::SC_MAP_UNLOADED:
- enable_autonestbox = false;
- enable_autobutcher = false;
- // cleanup
- cleanup_autobutcher(out);
- cleanup_autonestbox(out);
- break;
- default:
- break;
- }
- return CR_OK;
- }
- DFhackCExport command_result plugin_onupdate ( color_ostream &out )
- {
- static size_t ticks_autonestbox = 0;
- static size_t ticks_autobutcher = 0;
- if(enable_autonestbox)
- {
- if(++ticks_autonestbox >= sleep_autonestbox)
- {
- ticks_autonestbox = 0;
- autoNestbox(out, false);
- }
- }
- if(enable_autobutcher)
- {
- if(++ticks_autobutcher >= sleep_autobutcher)
- {
- ticks_autobutcher= 0;
- autoButcher(out, false);
- }
- }
- return CR_OK;
- }
- ///////////////
- // Various small tool functions
- // probably many of these should be moved to Unit.h and Building.h sometime later...
- int32_t getUnitAge(df::unit* unit);
- bool isTame(df::unit* unit);
- bool isTrained(df::unit* unit);
- bool isWar(df::unit* unit);
- bool isHunter(df::unit* unit);
- bool isOwnCiv(df::unit* unit);
- bool isMerchant(df::unit* unit);
- bool isForest(df::unit* unit);
- bool isActivityZone(df::building * building);
- bool isPenPasture(df::building * building);
- bool isPitPond(df::building * building);
- bool isActive(df::building * building);
- int32_t findBuildingIndexById(int32_t id);
- int32_t findPenPitAtCursor();
- int32_t findCageAtCursor();
- int32_t findChainAtCursor();
- df::general_ref_building_civzone_assignedst * createCivzoneRef();
- bool unassignUnitFromBuilding(df::unit* unit);
- command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose);
- void unitInfo(color_ostream & out, df::unit* creature, bool verbose);
- void zoneInfo(color_ostream & out, df::building* building, bool verbose);
- void cageInfo(color_ostream & out, df::building* building, bool verbose);
- void chainInfo(color_ostream & out, df::building* building, bool verbose);
- bool isBuiltCageAtPos(df::coord pos);
- bool isInBuiltCageRoom(df::unit*);
- bool isNaked(df::unit *);
- bool isTamable(df::unit *);
- int32_t getUnitAge(df::unit* unit)
- {
- // If the birthday this year has not yet passed, subtract one year.
- // ASSUMPTION: birth_time is on the same scale as cur_year_tick
- int32_t yearDifference = *df::global::cur_year - unit->relations.birth_year;
- if (unit->relations.birth_time >= *df::global::cur_year_tick)
- yearDifference--;
- return yearDifference;
- }
- bool isDead(df::unit* unit)
- {
- return unit->flags1.bits.dead;
- }
- // ignore vampires, they should be treated like normal dwarves
- bool isUndead(df::unit* unit)
- {
- return (unit->flags3.bits.ghostly ||
- ( (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE || unit->curse.add_tags1.bits.NOT_LIVING)
- && !unit->curse.add_tags1.bits.BLOODSUCKER ));
- }
- bool isMerchant(df::unit* unit)
- {
- return unit->flags1.bits.merchant;
- }
- bool isForest(df::unit* unit)
- {
- return unit->flags1.bits.forest;
- }
- bool isMarkedForSlaughter(df::unit* unit)
- {
- return unit->flags2.bits.slaughter;
- }
- void doMarkForSlaughter(df::unit* unit)
- {
- unit->flags2.bits.slaughter = 1;
- }
- // check if creature is tame
- bool isTame(df::unit* creature)
- {
- bool tame = false;
- if(creature->flags1.bits.tame)
- {
- switch (creature->training_level)
- {
- case df::animal_training_level::SemiWild: //??
- case df::animal_training_level::Trained:
- case df::animal_training_level::WellTrained:
- case df::animal_training_level::SkilfullyTrained:
- case df::animal_training_level::ExpertlyTrained:
- case df::animal_training_level::ExceptionallyTrained:
- case df::animal_training_level::MasterfullyTrained:
- case df::animal_training_level::Domesticated:
- tame=true;
- break;
- case df::animal_training_level::Unk8: //??
- case df::animal_training_level::WildUntamed:
- default:
- tame=false;
- break;
- }
- }
- return tame;
- }
- // check if creature is domesticated
- // seems to be the only way to really tell if it's completely safe to autonestbox it (training can revert)
- bool isDomesticated(df::unit* creature)
- {
- bool tame = false;
- if(creature->flags1.bits.tame)
- {
- switch (creature->training_level)
- {
- case df::animal_training_level::Domesticated:
- tame=true;
- break;
- default:
- tame=false;
- break;
- }
- }
- return tame;
- }
- // check if trained (might be useful if pasturing war dogs etc)
- bool isTrained(df::unit* unit)
- {
- // case a: trained for war/hunting (those don't have a training level, strangely)
- if(isWar(unit) || isHunter(unit))
- return true;
- // case b: tamed and trained wild creature, gets a training level
- bool trained = false;
- switch (unit->training_level)
- {
- case df::animal_training_level::Trained:
- case df::animal_training_level::WellTrained:
- case df::animal_training_level::SkilfullyTrained:
- case df::animal_training_level::ExpertlyTrained:
- case df::animal_training_level::ExceptionallyTrained:
- case df::animal_training_level::MasterfullyTrained:
- //case df::animal_training_level::Domesticated:
- trained = true;
- break;
- default:
- break;
- }
- return trained;
- }
- // check for profession "war creature"
- bool isWar(df::unit* unit)
- {
- if( unit->profession == df::profession::TRAINED_WAR
- || unit->profession2 == df::profession::TRAINED_WAR)
- return true;
- else
- return false;
- }
- // check for profession "hunting creature"
- bool isHunter(df::unit* unit)
- {
- if( unit->profession == df::profession::TRAINED_HUNTER
- || unit->profession2 == df::profession::TRAINED_HUNTER)
- return true;
- else
- return false;
- }
- // check if creature belongs to the player's civilization
- // (don't try to pasture/slaughter random untame animals)
- bool isOwnCiv(df::unit* unit)
- {
- return unit->civ_id == ui->civ_id;
- }
- // check if creature belongs to the player's race
- // (in combination with check for civ helps to filter out own dwarves)
- bool isOwnRace(df::unit* unit)
- {
- return unit->race == ui->race_id;
- }
- string getRaceName(int32_t id)
- {
- df::creature_raw *raw = df::global::world->raws.creatures.all[id];
- return raw->creature_id;
- }
- string getRaceName(df::unit* unit)
- {
- df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
- return raw->creature_id;
- }
- string getRaceBabyName(df::unit* unit)
- {
- df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
- return raw->general_baby_name[0];
- }
- string getRaceChildName(df::unit* unit)
- {
- df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
- return raw->general_child_name[0];
- }
- bool isBaby(df::unit* unit)
- {
- return unit->profession == df::profession::BABY;
- }
- bool isChild(df::unit* unit)
- {
- return unit->profession == df::profession::CHILD;
- }
- bool isAdult(df::unit* unit)
- {
- return !isBaby(unit) && !isChild(unit);
- }
- bool isEggLayer(df::unit* unit)
- {
- df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
- size_t sizecas = raw->caste.size();
- for (size_t j = 0; j < sizecas;j++)
- {
- df::caste_raw *caste = raw->caste[j];
- if( caste->flags.is_set(caste_raw_flags::LAYS_EGGS)
- || caste->flags.is_set(caste_raw_flags::LAYS_UNUSUAL_EGGS))
- return true;
- }
- return false;
- }
- bool isGrazer(df::unit* unit)
- {
- df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
- size_t sizecas = raw->caste.size();
- for (size_t j = 0; j < sizecas;j++)
- {
- df::caste_raw *caste = raw->caste[j];
- if(caste->flags.is_set(caste_raw_flags::GRAZER))
- return true;
- }
- return false;
- }
- bool isMilkable(df::unit* unit)
- {
- df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
- size_t sizecas = raw->caste.size();
- for (size_t j = 0; j < sizecas;j++)
- {
- df::caste_raw *caste = raw->caste[j];
- if(caste->flags.is_set(caste_raw_flags::MILKABLE))
- return true;
- }
- return false;
- }
- bool isTrainableWar(df::unit* unit)
- {
- df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
- size_t sizecas = raw->caste.size();
- for (size_t j = 0; j < sizecas;j++)
- {
- df::caste_raw *caste = raw->caste[j];
- if(caste->flags.is_set(caste_raw_flags::TRAINABLE_WAR))
- return true;
- }
- return false;
- }
- bool isTrainableHunting(df::unit* unit)
- {
- df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
- size_t sizecas = raw->caste.size();
- for (size_t j = 0; j < sizecas;j++)
- {
- df::caste_raw *caste = raw->caste[j];
- if(caste->flags.is_set(caste_raw_flags::TRAINABLE_HUNTING))
- return true;
- }
- return false;
- }
- bool isTamable(df::unit* unit)
- {
- df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
- size_t sizecas = raw->caste.size();
- for (size_t j = 0; j < sizecas;j++)
- {
- df::caste_raw *caste = raw->caste[j];
- if(caste->flags.is_set(caste_raw_flags::PET) ||
- caste->flags.is_set(caste_raw_flags::PET_EXOTIC))
- return true;
- }
- return false;
- }
- bool isMale(df::unit* unit)
- {
- return unit->sex == 1;
- }
- bool isFemale(df::unit* unit)
- {
- return unit->sex == 0;
- }
- // found a unit with weird position values on one of my maps (negative and in the thousands)
- // it didn't appear in the animal stocks screen, but looked completely fine otherwise (alive, tame, own, etc)
- // maybe a rare bug, but better avoid assigning such units to zones or slaughter etc.
- bool hasValidMapPos(df::unit* unit)
- {
- if( unit->pos.x >=0 && unit->pos.y >= 0 && unit->pos.z >= 0
- && unit->pos.x < world->map.x_count
- && unit->pos.y < world->map.y_count
- && unit->pos.z < world->map.z_count)
- return true;
- else
- return false;
- }
- bool isNaked(df::unit* unit)
- {
- return (unit->inventory.empty());
- }
- int getUnitIndexFromId(df::unit* unit_)
- {
- for (size_t i=0; i < world->units.all.size(); i++)
- {
- df::unit* unit = world->units.all[i];
- if(unit->id == unit_->id)
- return i;
- }
- return -1;
- }
- // dump some unit info
- void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false)
- {
- out.print("Unit %d ", unit->id); //race %d, civ %d,", creature->race, creature->civ_id
- if(unit->name.has_name)
- {
- // units given a nick with the rename tool might not have a first name (animals etc)
- string firstname = unit->name.first_name;
- if(firstname.size() > 0)
- {
- firstname[0] = toupper(firstname[0]);
- out << "Name: " << firstname;
- }
- if(unit->name.nickname.size() > 0)
- out << " '" << unit->name.nickname << "'";
- out << ", ";
- }
- if(isAdult(unit))
- out << "adult";
- else if(isBaby(unit))
- out << "baby";
- else if(isChild(unit))
- out << "child";
- out << " ";
- // sometimes empty even in vanilla RAWS, sometimes contains full race name (e.g. baby alpaca)
- // all animals I looked at don't have babies anyways, their offspring starts as CHILD
- //out << getRaceBabyName(unit);
- //out << getRaceChildName(unit);
- out << getRaceName(unit) << " (";
- switch(unit->sex)
- {
- case 0:
- out << "female";
- break;
- case 1:
- out << "male";
- break;
- case -1:
- default:
- out << "n/a";
- break;
- }
- out << ")";
- out << ", age: " << getUnitAge(unit);
- if(isTame(unit))
- out << ", tame";
- if(isOwnCiv(unit))
- out << ", owned";
- if(isWar(unit))
- out << ", war";
- if(isHunter(unit))
- out << ", hunter";
- if(isMerchant(unit))
- out << ", merchant";
- if(isForest(unit))
- out << ", forest";
- if(isEggLayer(unit))
- out << ", egglayer";
- if(isGrazer(unit))
- out << ", grazer";
- if(isMilkable(unit))
- out << ", milkable";
- if(verbose)
- {
- out << ". Pos: ("<<unit->pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ") " << endl;
- out << "index in units vector: " << getUnitIndexFromId(unit) << endl;
- }
- out << endl;
- if(!verbose)
- return;
- //out << "number of refs: " << creature->general_refs.size() << endl;
- for(size_t r = 0; r<unit->general_refs.size(); r++)
- {
- df::general_ref* ref = unit->general_refs.at(r);
- df::general_ref_type refType = ref->getType();
- out << " ref#" << r <<" refType#" << refType << " "; //endl;
- switch(refType)
- {
- case df::general_ref_type::BUILDING_CIVZONE_ASSIGNED:
- {
- out << "assigned to zone";
- df::building_civzonest * civAss = (df::building_civzonest *) ref->getBuilding();
- out << " #" << civAss->id;
- }
- break;
- case df::general_ref_type::CONTAINED_IN_ITEM:
- out << "contained in item";
- break;
- case df::general_ref_type::BUILDING_CAGED:
- out << "caged";
- break;
- case df::general_ref_type::BUILDING_CHAIN:
- out << "chained";
- break;
- default:
- //out << "unhandled reftype";
- break;
- }
- out << endl;
- }
- if(isInBuiltCageRoom(unit))
- {
- out << "in a room." << endl;
- }
- }
- bool isActivityZone(df::building * building)
- {
- if( building->getType() == building_type::Civzone
- && building->getSubtype() == (short)civzone_type::ActivityZone)
- return true;
- else
- return false;
- }
- bool isPenPasture(df::building * building)
- {
- if(!isActivityZone(building))
- return false;
- df::building_civzonest * civ = (df::building_civzonest *) building;
- if(civ->zone_flags.bits.pen_pasture)
- return true;
- else
- return false;
- }
- bool isPitPond(df::building * building)
- {
- if(!isActivityZone(building))
- return false;
- df::building_civzonest * civ = (df::building_civzonest *) building;
- if(civ->zone_flags.bits.pit_pond) // && civ->pit_flags==0)
- return true;
- else
- return false;
- }
- bool isCage(df::building * building)
- {
- return building->getType() == building_type::Cage;
- }
- bool isChain(df::building * building)
- {
- return building->getType() == building_type::Chain;
- }
- bool isActive(df::building * building)
- {
- if(!isActivityZone(building))
- return false;
- df::building_civzonest * civ = (df::building_civzonest *) building;
- if(civ->zone_flags.bits.active)
- return true;
- else
- return false;
- }
- int32_t findBuildingIndexById(int32_t id)
- {
- for (size_t b = 0; b < world->buildings.all.size(); b++)
- {
- if(world->buildings.all.at(b)->id == id)
- return b;
- }
- return -1;
- }
- int32_t findUnitIndexById(int32_t id)
- {
- for (size_t i = 0; i < world->units.all.size(); i++)
- {
- if(world->units.all.at(i)->id == id)
- return i;
- }
- return -1;
- }
- df::unit* findUnitById(int32_t id)
- {
- int32_t index = findUnitIndexById(id);
- if(index != -1)
- return world->units.all[index];
- else
- return NULL;
- }
- // returns id of pen/pit at cursor position (-1 if nothing found)
- int32_t findPenPitAtCursor()
- {
- int32_t foundID = -1;
- if(cursor->x == -30000)
- return -1;
- for (size_t b = 0; b < world->buildings.all.size(); b++)
- {
- df::building* building = world->buildings.all[b];
- // find zone under cursor
- if (!(building->x1 <= cursor->x && cursor->x <= building->x2 &&
- building->y1 <= cursor->y && cursor->y <= building->y2 &&
- building->z == cursor->z))
- continue;
- if(isPenPasture(building) || isPitPond(building))
- {
- foundID = building->id;
- break;
- }
- }
- return foundID;
- }
- // returns id of cage at cursor position (-1 if nothing found)
- int32_t findCageAtCursor()
- {
- int32_t foundID = -1;
- if(cursor->x == -30000)
- return -1;
- for (size_t b = 0; b < world->buildings.all.size(); b++)
- {
- df::building* building = world->buildings.all[b];
- if (!(building->x1 <= cursor->x && cursor->x <= building->x2 &&
- building->y1 <= cursor->y && cursor->y <= building->y2 &&
- building->z == cursor->z))
- continue;
- // don't set id if cage is not constructed yet
- if(building->getBuildStage()!=building->getMaxBuildStage())
- break;
- if(isCage(building))
- {
- foundID = building->id;
- break;
- }
- }
- return foundID;
- }
- int32_t findChainAtCursor()
- {
- int32_t foundID = -1;
- if(cursor->x == -30000)
- return -1;
- for (size_t b = 0; b < world->buildings.all.size(); b++)
- {
- df::building* building = world->buildings.all[b];
- // find zone under cursor
- if (!(building->x1 <= cursor->x && cursor->x <= building->x2 &&
- building->y1 <= cursor->y && cursor->y <= building->y2 &&
- building->z == cursor->z))
- continue;
- if(isChain(building))
- {
- foundID = building->id;
- break;
- }
- }
- return foundID;
- }
- df::general_ref_building_civzone_assignedst * createCivzoneRef()
- {
- static bool vt_initialized = false;
- df::general_ref_building_civzone_assignedst* newref = NULL;
- // after having run successfully for the first time it's safe to simply create the object
- if(vt_initialized)
- {
- newref = (df::general_ref_building_civzone_assignedst*)
- df::general_ref_building_civzone_assignedst::_identity.instantiate();
- return newref;
- }
- // being called for the first time, need to initialize the vtable
- for(size_t i = 0; i < world->units.all.size(); i++)
- {
- df::unit * creature = world->units.all[i];
- for(size_t r = 0; r<creature->general_refs.size(); r++)
- {
- df::general_ref* ref;
- ref = creature->general_refs.at(r);
- if(ref->getType() == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED)
- {
- if (strict_virtual_cast<df::general_ref_building_civzone_assignedst>(ref))
- {
- // !! calling new() doesn't work, need _identity.instantiate() instead !!
- newref = (df::general_ref_building_civzone_assignedst*)
- df::general_ref_building_civzone_assignedst::_identity.instantiate();
- vt_initialized = true;
- break;
- }
- }
- }
- if(vt_initialized)
- break;
- }
- return newref;
- }
- bool isInBuiltCage(df::unit* unit);
- // check if assigned to pen, pit, (built) cage or chain
- // note: BUILDING_CAGED is not set for animals (maybe it's used for dwarves who get caged as sentence)
- // animals in cages (no matter if built or on stockpile) get the ref CONTAINED_IN_ITEM instead
- // removing them from cages on stockpiles is no problem even without clearing the ref
- // and usually it will be desired behavior to do so.
- bool isAssigned(df::unit* unit)
- {
- bool assigned = false;
- for (size_t r=0; r < unit->general_refs.size(); r++)
- {
- df::general_ref * ref = unit->general_refs[r];
- auto rtype = ref->getType();
- if( rtype == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED
- || rtype == df::general_ref_type::BUILDING_CAGED
- || rtype == df::general_ref_type::BUILDING_CHAIN
- || (rtype == df::general_ref_type::CONTAINED_IN_ITEM && isInBuiltCage(unit))
- )
- {
- assigned = true;
- break;
- }
- }
- return assigned;
- }
- // check if assigned to a chain or built cage
- // (need to check if the ref needs to be removed, until then touching them is forbidden)
- bool isChained(df::unit* unit)
- {
- bool contained = false;
- for (size_t r=0; r < unit->general_refs.size(); r++)
- {
- df::general_ref * ref = unit->general_refs[r];
- auto rtype = ref->getType();
- if(rtype == df::general_ref_type::BUILDING_CHAIN)
- {
- contained = true;
- break;
- }
- }
- return contained;
- }
- // check if contained in item (e.g. animals in cages)
- bool isContainedInItem(df::unit* unit)
- {
- bool contained = false;
- for (size_t r=0; r < unit->general_refs.size(); r++)
- {
- df::general_ref * ref = unit->general_refs[r];
- auto rtype = ref->getType();
- if(rtype == df::general_ref_type::CONTAINED_IN_ITEM)
- {
- contained = true;
- break;
- }
- }
- return contained;
- }
- bool isInBuiltCage(df::unit* unit)
- {
- bool caged = false;
- for (size_t b=0; b < world->buildings.all.size(); b++)
- {
- df::building* building = world->buildings.all[b];
- if( building->getType() == building_type::Cage)
- {
- df::building_cagest* cage = (df::building_cagest*) building;
- for(size_t c=0; c<cage->assigned_creature.size(); c++)
- {
- if(cage->assigned_creature[c] == unit->id)
- {
- caged = true;
- break;
- }
- }
- }
- if(caged)
- break;
- }
- return caged;
- }
- // built cage defined as room (supposed to detect zoo cages)
- bool isInBuiltCageRoom(df::unit* unit)
- {
- bool caged_room = false;
- for (size_t b=0; b < world->buildings.all.size(); b++)
- {
- df::building* building = world->buildings.all[b];
- // !!! building->isRoom() returns true if the building can be made a room but currently isn't
- // !!! except for coffins/tombs which always return false
- // !!! using the bool is_room however gives the correct state/value
- if(!building->is_room)
- continue;
- if(building->getType() == building_type::Cage)
- {
- df::building_cagest* cage = (df::building_cagest*) building;
- for(size_t c=0; c<cage->assigned_creature.size(); c++)
- {
- if(cage->assigned_creature[c] == unit->id)
- {
- caged_room = true;
- break;
- }
- }
- }
- if(caged_room)
- break;
- }
- return caged_room;
- }
- // check a map position for a built cage
- // animals in cages are CONTAINED_IN_ITEM, no matter if they are on a stockpile or inside a built cage
- // if they are on animal stockpiles they should count as unassigned to allow pasturing them
- // if they are inside built cages they should be ignored in case the cage is a zoo or linked to a lever or whatever
- bool isBuiltCageAtPos(df::coord pos)
- {
- bool cage = false;
- for (size_t b=0; b < world->buildings.all.size(); b++)
- {
- df::building* building = world->buildings.all[b];
- if( building->getType() == building_type::Cage
- && building->x1 == pos.x
- && building->y1 == pos.y
- && building->z == pos.z )
- {
- cage = true;
- break;
- }
- }
- return cage;
- }
- df::building * getBuiltCageAtPos(df::coord pos)
- {
- df::building* cage = NULL;
- for (size_t b=0; b < world->buildings.all.size(); b++)
- {
- df::building* building = world->buildings.all[b];
- if( building->getType() == building_type::Cage
- && building->x1 == pos.x
- && building->y1 == pos.y
- && building->z == pos.z )
- {
- // don't set pointer if not constructed yet
- if(building->getBuildStage()!=building->getMaxBuildStage())
- break;
- cage = building;
- break;
- }
- }
- return cage;
- }
- bool isNestboxAtPos(int32_t x, int32_t y, int32_t z)
- {
- bool found = false;
- for (size_t b=0; b < world->buildings.all.size(); b++)
- {
- df::building* building = world->buildings.all[b];
- if( building->getType() == building_type::NestBox
- && building->x1 == x
- && building->y1 == y
- && building->z == z )
- {
- found = true;
- break;
- }
- }
- return found;
- }
- bool isFreeNestboxAtPos(int32_t x, int32_t y, int32_t z)
- {
- bool found = false;
- for (size_t b=0; b < world->buildings.all.size(); b++)
- {
- df::building* building = world->buildings.all[b];
- if( building->getType() == building_type::NestBox
- && building->x1 == x
- && building->y1 == y
- && building->z == z )
- {
- df::building_nest_boxst* nestbox = (df::building_nest_boxst*) building;
- if(nestbox->claimed_by == -1 && nestbox->contained_items.size() == 1)
- {
- found = true;
- break;
- }
- }
- }
- return found;
- }
- bool isEmptyPasture(df::building* building)
- {
- if(!isPenPasture(building))
- return false;
- df::building_civzonest * civ = (df::building_civzonest *) building;
- if(civ->assigned_creature.size() == 0)
- return true;
- else
- return false;
- }
- df::building* findFreeNestboxZone()
- {
- df::building * free_building = NULL;
- bool cage = false;
- for (size_t b=0; b < world->buildings.all.size(); b++)
- {
- df::building* building = world->buildings.all[b];
- if( isEmptyPasture(building) &&
- isActive(building) &&
- isFreeNestboxAtPos(building->x1, building->y1, building->z))
- {
- free_building = building;
- break;
- }
- }
- return free_building;
- }
- bool isFreeEgglayer(df::unit * unit)
- {
- if( !isDead(unit) && !isUndead(unit)
- && isFemale(unit)
- && isTame(unit)
- && isOwnCiv(unit)
- && isEggLayer(unit)
- && !isAssigned(unit)
- && !isGrazer(unit) // exclude grazing birds because they're messy
- && !isMerchant(unit) // don't steal merchant mounts
- && !isForest(unit) // don't steal birds from traders, they hate that
- )
- return true;
- else
- return false;
- }
- df::unit * findFreeEgglayer()
- {
- df::unit* free_unit = NULL;
- for (size_t i=0; i < world->units.all.size(); i++)
- {
- df::unit* unit = world->units.all[i];
- if(isFreeEgglayer(unit))
- {
- free_unit = unit;
- break;
- }
- }
- return free_unit;
- }
- size_t countFreeEgglayers()
- {
- size_t count = 0;
- for (size_t i=0; i < world->units.all.size(); i++)
- {
- df::unit* unit = world->units.all[i];
- if(isFreeEgglayer(unit))
- count ++;
- }
- return count;
- }
- // check if unit is already assigned to a zone, remove that ref from unit and old zone
- // check if unit is already assigned to a cage, remove that ref from the cage
- // returns false if no cage or pasture information was found
- // helps as workaround for http://www.bay12games.com/dwarves/mantisbt/view.php?id=4475 by the way
- // (pastured animals assigned to chains will get hauled back and forth because the pasture ref is not deleted)
- bool unassignUnitFromBuilding(df::unit* unit)
- {
- bool success = false;
- for (std::size_t idx = 0; idx < unit->general_refs.size(); idx++)
- {
- df::general_ref * oldref = unit->general_refs[idx];
- switch(oldref->getType())
- {
- case df::general_ref_type::BUILDING_CIVZONE_ASSIGNED:
- {
- unit->general_refs.erase(unit->general_refs.begin() + idx);
- df::building_civzonest * oldciv = (df::building_civzonest *) oldref->getBuilding();
- for(size_t oc=0; oc<oldciv->assigned_creature.size(); oc++)
- {
- if(oldciv->assigned_creature[oc] == unit->id)
- {
- oldciv->assigned_creature.erase(oldciv->assigned_creature.begin() + oc);
- break;
- }
- }
- delete oldref;
- success = true;
- break;
- }
- case df::general_ref_type::CONTAINED_IN_ITEM:
- {
- // game does not erase the ref until creature gets removed from cage
- //unit->general_refs.erase(unit->general_refs.begin() + idx);
- // walk through buildings, check cages for inhabitants, compare ids
- for (size_t b=0; b < world->buildings.all.size(); b++)
- {
- bool found = false;
- df::building* building = world->buildings.all[b];
- if(isCage(building))
- {
- df::building_cagest* oldcage = (df::building_cagest*) building;
- for(size_t oc=0; oc<oldcage->assigned_creature.size(); oc++)
- {
- if(oldcage->assigned_creature[oc] == unit->id)
- {
- oldcage->assigned_creature.erase(oldcage->assigned_creature.begin() + oc);
- found = true;
- break;
- }
- }
- }
- if(found)
- break;
- }
- success = true;
- break;
- }
- case df::general_ref_type::BUILDING_CHAIN:
- {
- // try not erasing the ref and see what happens
- //unit->general_refs.erase(unit->general_refs.begin() + idx);
- // probably need to delete chain reference here
- //success = true;
- break;
- }
- case df::general_ref_type::BUILDING_CAGED:
- {
- // not sure what to do here, doesn't seem to get used by the game
- //unit->general_refs.erase(unit->general_refs.begin() + idx);
- //success = true;
- break;
- }
- default:
- {
- // some reference which probably shouldn't get deleted
- // (animals who are historical figures and have a NEMESIS reference or whatever)
- break;
- }
- }
- }
- return success;
- }
- // assign to pen or pit
- command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false)
- {
- // building must be a pen/pasture or pit
- if(!isPenPasture(building) && !isPitPond(building))
- {
- out << "Invalid building type. This is not a pen/pasture or pit/pond." << endl;
- return CR_WRONG_USAGE;
- }
- // try to get a fresh civzone ref
- df::general_ref_building_civzone_assignedst * ref = createCivzoneRef();
- if(!ref)
- {
- out << "Could not find a clonable activity zone reference" << endl
- << "You need to pen/pasture/pit at least one creature" << endl
- << "before using 'assign' for the first time." << endl;
- return CR_WRONG_USAGE;
- }
- // check if unit is already pastured, remove that ref from unit and old pasture
- // testing showed that removing the ref from the unit only seems to be necessary for pastured creatures
- // if they are in cages on stockpiles the game unassigns them automatically
- // if they are in built cages the pointer to the creature needs to be removed from the cage
- // TODO: check what needs to be done for chains
- bool cleared_old = unassignUnitFromBuilding(unit);
- if(verbose)
- {
- if(cleared_old)
- out << "old zone info cleared.";
- else
- out << "no old zone info found.";
- }
- ref->building_id = building->id;
- unit->general_refs.push_back(ref);
- df::building_civzonest * civz = (df::building_civzonest *) building;
- civz->assigned_creature.push_back(unit->id);
- out << "Unit " << unit->id
- << "(" << getRaceName(unit) << ")"
- << " assigned to zone " << building->id;
- if(isPitPond(building))
- out << " (pit/pond).";
- if(isPenPasture(building))
- out << " (pen/pasture).";
- out << endl;
- return CR_OK;
- }
- command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building* building, bool verbose)
- {
- // building must be a pen/pasture or pit
- if(!isCage(building))
- {
- out << "Invalid building type. This is not a cage." << endl;
- return CR_WRONG_USAGE;
- }
- // don't assign owned pets to a cage. the owner will release them, resulting into infinite hauling (df bug)
- if(unit->relations.pet_owner_id != -1)
- return CR_OK;
- // check if unit is already pastured or caged, remove refs where necessary
- bool cleared_old = unassignUnitFromBuilding(unit);
- if(verbose)
- {
- if(cleared_old)
- out << "old zone info cleared.";
- else
- out << "no old zone info found.";
- }
- //ref->building_id = building->id;
- //unit->general_refs.push_back(ref);
- df::building_cagest* civz = (df::building_cagest*) building;
- civz->assigned_creature.push_back(unit->id);
- out << "Unit " << unit->id
- << "(" << getRaceName(unit) << ")"
- << " assigned to cage " << building->id;
- out << endl;
- return CR_OK;
- }
- command_result assignUnitToChain(color_ostream& out, df::unit* unit, df::building* building, bool verbose)
- {
- out << "sorry. assigning to chains is not possible yet." << endl;
- return CR_WRONG_USAGE;
- }
- command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::building* building, bool verbose)
- {
- command_result result = CR_WRONG_USAGE;
- if(isActivityZone(building))
- result = assignUnitToZone(out, unit, building, verbose);
- else if(isCage(building))
- result = assignUnitToCage(out, unit, building, verbose);
- else if(isChain(building))
- result = assignUnitToChain(out, unit, building, verbose);
- else
- out << "Cannot assign units to this type of building!" << endl;
- return result;
- }
- command_result assignUnitsToCagezone(color_ostream& out, vector<df::unit*> units, df::building* building, bool verbose)
- {
- command_result result = CR_WRONG_USAGE;
- if(!isPenPasture(building))
- {
- out << "A cage zone needs to be a pen/pasture containing at least one cage!" << endl;
- return CR_WRONG_USAGE;
- }
- int32_t x1 = building->x1;
- int32_t x2 = building->x2;
- int32_t y1 = building->y1;
- int32_t y2 = building->y2;
- int32_t z = building->z;
- vector <df::building_cagest*> cages;
- for (int32_t x = x1; x<=x2; x++)
- {
- for (int32_t y = y1; y<=y2; y++)
- {
- df::building* cage = getBuiltCageAtPos(df::coord(x,y,z));
- if(cage)
- {
- df::building_cagest* cagest = (df::building_cagest*) cage;
- cages.push_back(cagest);
- }
- }
- }
- if(!cages.size())
- {
- out << "No cages found in this zone!" << endl;
- return CR_WRONG_USAGE;
- }
- else
- {
- out << "Number of cages: " << cages.size() << endl;
- }
- while(units.size())
- {
- // hrm, better use sort() instead?
- df::building_cagest * bestcage = cages[0];
- size_t lowest = cages[0]->assigned_creature.size();
- for(size_t i=1; i<cages.size(); i++)
- {
- …
Large files files are truncated, but you can click here to view the full file