PageRenderTime 70ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/plugins/zone.cpp

https://github.com/mokerjoke/dfhack
C++ | 3547 lines | 3022 code | 306 blank | 219 comment | 886 complexity | c94dc09a2459f59d624b7b4d0a843c7e 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

  1. // Intention: help with activity zone management (auto-pasture animals, auto-pit goblins, ...)
  2. //
  3. // the following things would be nice:
  4. // - dump info about pastures, pastured animals, count non-pastured tame animals, print gender info
  5. // - help finding caged dwarves? (maybe even allow to build their cages for fast release)
  6. // - dump info about caged goblins, animals, ...
  7. // - count grass tiles on pastures, move grazers to new pasture if old pasture is empty
  8. // move hungry unpastured grazers to pasture with grass
  9. //
  10. // What is working so far:
  11. // - print detailed info about activity zone and units under cursor (mostly for checking refs and stuff)
  12. // - mark a zone which is used for future assignment commands
  13. // - assign single selected creature to a zone
  14. // - mass-assign creatures using filters
  15. // - unassign single creature under cursor from current zone
  16. // - pitting own dwarves :)
  17. // - full automation of handling mini-pastures over nestboxes:
  18. // go through all pens, check if they are empty and placed over a nestbox
  19. // find female tame egg-layer who is not assigned to another pen and assign it to nestbox pasture
  20. // maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds
  21. // state and sleep setting is saved the first time autonestbox is started (to avoid writing stuff if the plugin is never used)
  22. // - full automation of marking live-stock for slaughtering
  23. // races can be added to a watchlist and it can be set how many male/female kids/adults are left alive
  24. // adding to the watchlist can be automated as well.
  25. // config for autobutcher (state and sleep setting) is saved the first time autobutcher is started
  26. // config for watchlist entries is saved when they are created or modified
  27. #include <iostream>
  28. #include <iomanip>
  29. #include <climits>
  30. #include <vector>
  31. #include <algorithm>
  32. #include <string>
  33. #include <sstream>
  34. #include <ctime>
  35. #include <cstdio>
  36. using namespace std;
  37. #include "Core.h"
  38. #include "Console.h"
  39. #include "Export.h"
  40. #include "PluginManager.h"
  41. #include "modules/Units.h"
  42. #include "modules/Maps.h"
  43. #include "modules/Gui.h"
  44. #include "modules/Materials.h"
  45. #include "modules/MapCache.h"
  46. #include "modules/Buildings.h"
  47. #include "modules/World.h"
  48. #include "MiscUtils.h"
  49. #include <df/ui.h>
  50. #include "df/world.h"
  51. #include "df/world_raws.h"
  52. #include "df/building_def.h"
  53. #include "df/building_civzonest.h"
  54. #include "df/building_cagest.h"
  55. #include "df/building_chainst.h"
  56. #include "df/building_nest_boxst.h"
  57. #include "df/general_ref_building_civzone_assignedst.h"
  58. #include <df/creature_raw.h>
  59. #include <df/caste_raw.h>
  60. using std::vector;
  61. using std::string;
  62. using namespace DFHack;
  63. using namespace df::enums;
  64. using df::global::world;
  65. using df::global::cursor;
  66. using df::global::ui;
  67. using namespace DFHack::Gui;
  68. command_result df_zone (color_ostream &out, vector <string> & parameters);
  69. command_result df_autonestbox (color_ostream &out, vector <string> & parameters);
  70. command_result df_autobutcher(color_ostream &out, vector <string> & parameters);
  71. DFHACK_PLUGIN("zone");
  72. const string zone_help =
  73. "Allows easier management of pens/pastures, pits and cages.\n"
  74. "Options:\n"
  75. " set - set zone under cursor as default for future assigns\n"
  76. " assign - assign creature(s) to a pen or pit\n"
  77. " if no filters are used, a single unit must be selected.\n"
  78. " can be followed by valid building id which will then be set.\n"
  79. " building must be a pen/pasture, pit or cage.\n"
  80. " slaughter - mark creature(s) for slaughter\n"
  81. " if no filters are used, a single unit must be selected.\n"
  82. " with filters named units are ignored unless specified.\n"
  83. " unassign - unassign selected creature(s) from it's zone or cage\n"
  84. " nick - give unit(s) nicknames (e.g. all units in a cage)\n"
  85. " remnick - remove nicknames\n"
  86. " tocages - assign to (multiple) built cages inside a pen/pasture\n"
  87. " spreads creatures evenly among cages for faster hauling.\n"
  88. " uinfo - print info about selected unit\n"
  89. " zinfo - print info about zone(s) under cursor\n"
  90. " verbose - print some more info, mostly useless debug stuff\n"
  91. " filters - print list of supported filters\n"
  92. " examples - print some usage examples\n"
  93. ;
  94. const string zone_help_filters =
  95. "Filters (to be used in combination with 'all' or 'count'):\n"
  96. "These filters can not be used with the prefix 'not':"
  97. " all - in combinations with zinfo/uinfo: print all zones/units\n"
  98. " in combination with assign: process all units\n"
  99. " should be used in combination with further filters\n"
  100. " count - must be followed by number. process X units\n"
  101. " should be used in combination with further filters\n"
  102. " unassigned - not assigned to zone, chain or built cage\n"
  103. " age - exact age. must be followed by number\n"
  104. " minage - minimum age. must be followed by number\n"
  105. " maxage - maximum age. must be followed by number\n"
  106. "These filters can be used with the prefix 'not' (e.g. 'not own'):"
  107. " race - must be followed by a race raw id (e.g. BIRD_TURKEY)\n"
  108. " caged - in a built cage\n"
  109. " own - from own civilization\n"
  110. " war - trained war creature\n"
  111. " tamed - tamed\n"
  112. " named - has name or nickname\n"
  113. " can be used to mark named units for slaughter\n"
  114. " merchant - is a merchant / belongs to a merchant\n"
  115. " can be used to pit merchants and slaughter their animals\n"
  116. " (could have weird effects during trading, be careful)\n"
  117. " trained - obvious\n"
  118. " trainablewar - can be trained for war (and is not already trained)\n"
  119. " trainablehunt- can be trained for hunting (and is not already trained)\n"
  120. " male - obvious\n"
  121. " female - obvious\n"
  122. " egglayer - race lays eggs (use together with 'female')\n"
  123. " grazer - is a grazer\n"
  124. " milkable - race is milkable (use together with 'female')\n"
  125. ;
  126. const string zone_help_examples =
  127. "Example for assigning single units:\n"
  128. " (ingame) move cursor to a pen/pasture or pit zone\n"
  129. " (dfhack) 'zone set' to use this zone for future assignments\n"
  130. " (dfhack) map 'zone assign' to a hotkey of your choice\n"
  131. " (ingame) select unit with 'v', 'k' or from unit list or inside a cage\n"
  132. " (ingame) press hotkey to assign unit to it's new home (or pit)\n"
  133. "Examples for assigning with filters:\n"
  134. " (this assumes you have already set up a target zone)\n"
  135. " zone assign all own grazer maxage 10\n"
  136. " zone assign all own milkable not grazer\n"
  137. " zone assign count 5 own female milkable\n"
  138. " zone assign all own race DWARF maxage 2\n"
  139. " throw all useless kids into a pit :)\n"
  140. "Notes:\n"
  141. " Unassigning per filters ignores built cages and chains currently. Usually you\n"
  142. " should always use the filter 'own' (which implies tame) unless you want to\n"
  143. " use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you\n"
  144. " specify 'race DWARF' and it ignores merchants and their animals unless you\n"
  145. " specify 'merchant' (so it's safe to use 'assign all own' to one big pasture\n"
  146. " if you want to have all your animals at the same place).\n"
  147. " 'egglayer' and 'milkable' should be used together with 'female'\n"
  148. " well, unless you have a mod with egg-laying male elves who give milk...\n";
  149. const string autonestbox_help =
  150. "Assigns unpastured female egg-layers to nestbox zones.\n"
  151. "Requires that you create pen/pasture zones above nestboxes.\n"
  152. "If the pen is bigger than 1x1 the nestbox must be in the top left corner.\n"
  153. "Only 1 unit will be assigned per pen, regardless of the size.\n"
  154. "The age of the units is currently not checked, most birds grow up quite fast.\n"
  155. "When called without options autonestbox will instantly run once.\n"
  156. "Options:\n"
  157. " start - run every X frames (df simulation ticks)\n"
  158. " default: X=6000 (~60 seconds at 100fps)\n"
  159. " stop - stop running automatically\n"
  160. " sleep X - change timer to sleep X frames between runs.\n";
  161. const string autobutcher_help =
  162. "Assigns your lifestock for slaughter once it reaches a specific count. Requires\n"
  163. "that you add the target race(s) to a watch list. Only tame units will be\n"
  164. "processed. Named units will be completely ignored (you can give animals\n"
  165. "nicknames with the tool 'rename unit' to protect them from getting slaughtered\n"
  166. "automatically. Trained war or hunting pets will be ignored.\n"
  167. "Once you have too much adults, the oldest will be butchered first.\n"
  168. "Once you have too much kids, the youngest will be butchered first.\n"
  169. "If you don't set a target count the following default will be used:\n"
  170. "1 male kid, 5 female kids, 1 male adult, 5 female adults.\n"
  171. "Options:\n"
  172. " start - run every X frames (df simulation ticks)\n"
  173. " default: X=6000 (~60 seconds at 100fps)\n"
  174. " stop - stop running automatically\n"
  175. " sleep X - change timer to sleep X frames between runs.\n"
  176. " watch R - start watching race(s)\n"
  177. " R = valid race RAW id (ALPACA, BIRD_TURKEY, etc)\n"
  178. " or a list of RAW ids seperated by spaces\n"
  179. " or the keyword 'all' which affects your whole current watchlist.\n"
  180. " unwatch R - stop watching race(s)\n"
  181. " the current target settings will be remembered\n"
  182. " forget R - unwatch race(s) and forget target settings for it/them\n"
  183. " autowatch - automatically adds all new races (animals you buy\n"
  184. " from merchants, tame yourself or get from migrants)\n"
  185. " to the watch list using default target count\n"
  186. " noautowatch - stop auto-adding new races to the watch list\n"
  187. " list - print status and watchlist\n"
  188. " list_export - print status and watchlist in batchfile format\n"
  189. " can be used to copy settings into another savegame\n"
  190. " usage: 'dfhack-run autobutcher list_export > xyz.bat' \n"
  191. " target fk mk fa ma R\n"
  192. " - set target count for specified race:\n"
  193. " fk = number of female kids\n"
  194. " mk = number of male kids\n"
  195. " fa = number of female adults\n"
  196. " ma = number of female adults\n"
  197. " R = 'all' sets count for all races on the current watchlist\n"
  198. " including the races which are currenly set to 'unwatched'\n"
  199. " and sets the new default for future watch commands\n"
  200. " R = 'new' sets the new default for future watch commands\n"
  201. " without changing your current watchlist\n"
  202. " example - print some usage examples\n";
  203. const string autobutcher_help_example =
  204. "Examples:\n"
  205. " autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY\n"
  206. " autobutcher watch ALPACA BIRD_TURKEY\n"
  207. " autobutcher start\n"
  208. " This means you want to have max 7 kids (4 female, 3 male) and max 3 adults\n"
  209. " (2 female, 1 male) of the races alpaca and turkey. Once the kids grow up the\n"
  210. " oldest adults will get slaughtered. Excess kids will get slaughtered starting\n"
  211. " the the youngest to allow that the older ones grow into adults.\n"
  212. " autobutcher target 0 0 0 0 new\n"
  213. " autobutcher autowatch\n"
  214. " autobutcher start\n"
  215. " This tells autobutcher to automatically put all new races onto the watchlist\n"
  216. " and mark unnamed tame units for slaughter as soon as they arrive in your\n"
  217. " fortress. Settings already made for some races will be left untouched.\n";
  218. command_result init_autobutcher(color_ostream &out);
  219. command_result cleanup_autobutcher(color_ostream &out);
  220. command_result start_autobutcher(color_ostream &out);
  221. command_result init_autonestbox(color_ostream &out);
  222. command_result cleanup_autonestbox(color_ostream &out);
  223. command_result start_autonestbox(color_ostream &out);
  224. DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
  225. {
  226. commands.push_back(PluginCommand(
  227. "zone", "manage activity zones.",
  228. df_zone, false,
  229. zone_help.c_str()
  230. ));
  231. commands.push_back(PluginCommand(
  232. "autonestbox", "auto-assign nestbox zones.",
  233. df_autonestbox, false,
  234. autonestbox_help.c_str()
  235. ));
  236. commands.push_back(PluginCommand(
  237. "autobutcher", "auto-assign lifestock for butchering.",
  238. df_autobutcher, false,
  239. autobutcher_help.c_str()
  240. ));
  241. init_autobutcher(out);
  242. init_autonestbox(out);
  243. return CR_OK;
  244. }
  245. DFhackCExport command_result plugin_shutdown ( color_ostream &out )
  246. {
  247. cleanup_autobutcher(out);
  248. cleanup_autonestbox(out);
  249. return CR_OK;
  250. }
  251. ///////////////
  252. // stuff for autonestbox and autobutcher
  253. // should be moved to own plugin once the tool methods it shares with the zone plugin are moved to Unit.h / Building.h
  254. command_result autoNestbox( color_ostream &out, bool verbose );
  255. command_result autoButcher( color_ostream &out, bool verbose );
  256. static bool enable_autonestbox = false;
  257. static bool enable_autobutcher = false;
  258. static bool enable_autobutcher_autowatch = false;
  259. static size_t sleep_autonestbox = 6000;
  260. static size_t sleep_autobutcher = 6000;
  261. static bool autonestbox_did_complain = false; // avoids message spam
  262. static PersistentDataItem config_autobutcher;
  263. static PersistentDataItem config_autonestbox;
  264. DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
  265. {
  266. switch (event)
  267. {
  268. case DFHack::SC_MAP_LOADED:
  269. // initialize from the world just loaded
  270. init_autobutcher(out);
  271. init_autonestbox(out);
  272. break;
  273. case DFHack::SC_MAP_UNLOADED:
  274. enable_autonestbox = false;
  275. enable_autobutcher = false;
  276. // cleanup
  277. cleanup_autobutcher(out);
  278. cleanup_autonestbox(out);
  279. break;
  280. default:
  281. break;
  282. }
  283. return CR_OK;
  284. }
  285. DFhackCExport command_result plugin_onupdate ( color_ostream &out )
  286. {
  287. static size_t ticks_autonestbox = 0;
  288. static size_t ticks_autobutcher = 0;
  289. if(enable_autonestbox)
  290. {
  291. if(++ticks_autonestbox >= sleep_autonestbox)
  292. {
  293. ticks_autonestbox = 0;
  294. autoNestbox(out, false);
  295. }
  296. }
  297. if(enable_autobutcher)
  298. {
  299. if(++ticks_autobutcher >= sleep_autobutcher)
  300. {
  301. ticks_autobutcher= 0;
  302. autoButcher(out, false);
  303. }
  304. }
  305. return CR_OK;
  306. }
  307. ///////////////
  308. // Various small tool functions
  309. // probably many of these should be moved to Unit.h and Building.h sometime later...
  310. int32_t getUnitAge(df::unit* unit);
  311. bool isTame(df::unit* unit);
  312. bool isTrained(df::unit* unit);
  313. bool isWar(df::unit* unit);
  314. bool isHunter(df::unit* unit);
  315. bool isOwnCiv(df::unit* unit);
  316. bool isMerchant(df::unit* unit);
  317. bool isForest(df::unit* unit);
  318. bool isActivityZone(df::building * building);
  319. bool isPenPasture(df::building * building);
  320. bool isPitPond(df::building * building);
  321. bool isActive(df::building * building);
  322. int32_t findBuildingIndexById(int32_t id);
  323. int32_t findPenPitAtCursor();
  324. int32_t findCageAtCursor();
  325. int32_t findChainAtCursor();
  326. df::general_ref_building_civzone_assignedst * createCivzoneRef();
  327. bool unassignUnitFromBuilding(df::unit* unit);
  328. command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose);
  329. void unitInfo(color_ostream & out, df::unit* creature, bool verbose);
  330. void zoneInfo(color_ostream & out, df::building* building, bool verbose);
  331. void cageInfo(color_ostream & out, df::building* building, bool verbose);
  332. void chainInfo(color_ostream & out, df::building* building, bool verbose);
  333. bool isBuiltCageAtPos(df::coord pos);
  334. bool isInBuiltCageRoom(df::unit*);
  335. bool isNaked(df::unit *);
  336. bool isTamable(df::unit *);
  337. int32_t getUnitAge(df::unit* unit)
  338. {
  339. // If the birthday this year has not yet passed, subtract one year.
  340. // ASSUMPTION: birth_time is on the same scale as cur_year_tick
  341. int32_t yearDifference = *df::global::cur_year - unit->relations.birth_year;
  342. if (unit->relations.birth_time >= *df::global::cur_year_tick)
  343. yearDifference--;
  344. return yearDifference;
  345. }
  346. bool isDead(df::unit* unit)
  347. {
  348. return unit->flags1.bits.dead;
  349. }
  350. // ignore vampires, they should be treated like normal dwarves
  351. bool isUndead(df::unit* unit)
  352. {
  353. return (unit->flags3.bits.ghostly ||
  354. ( (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE || unit->curse.add_tags1.bits.NOT_LIVING)
  355. && !unit->curse.add_tags1.bits.BLOODSUCKER ));
  356. }
  357. bool isMerchant(df::unit* unit)
  358. {
  359. return unit->flags1.bits.merchant;
  360. }
  361. bool isForest(df::unit* unit)
  362. {
  363. return unit->flags1.bits.forest;
  364. }
  365. bool isMarkedForSlaughter(df::unit* unit)
  366. {
  367. return unit->flags2.bits.slaughter;
  368. }
  369. void doMarkForSlaughter(df::unit* unit)
  370. {
  371. unit->flags2.bits.slaughter = 1;
  372. }
  373. // check if creature is tame
  374. bool isTame(df::unit* creature)
  375. {
  376. bool tame = false;
  377. if(creature->flags1.bits.tame)
  378. {
  379. switch (creature->training_level)
  380. {
  381. case df::animal_training_level::SemiWild: //??
  382. case df::animal_training_level::Trained:
  383. case df::animal_training_level::WellTrained:
  384. case df::animal_training_level::SkilfullyTrained:
  385. case df::animal_training_level::ExpertlyTrained:
  386. case df::animal_training_level::ExceptionallyTrained:
  387. case df::animal_training_level::MasterfullyTrained:
  388. case df::animal_training_level::Domesticated:
  389. tame=true;
  390. break;
  391. case df::animal_training_level::Unk8: //??
  392. case df::animal_training_level::WildUntamed:
  393. default:
  394. tame=false;
  395. break;
  396. }
  397. }
  398. return tame;
  399. }
  400. // check if creature is domesticated
  401. // seems to be the only way to really tell if it's completely safe to autonestbox it (training can revert)
  402. bool isDomesticated(df::unit* creature)
  403. {
  404. bool tame = false;
  405. if(creature->flags1.bits.tame)
  406. {
  407. switch (creature->training_level)
  408. {
  409. case df::animal_training_level::Domesticated:
  410. tame=true;
  411. break;
  412. default:
  413. tame=false;
  414. break;
  415. }
  416. }
  417. return tame;
  418. }
  419. // check if trained (might be useful if pasturing war dogs etc)
  420. bool isTrained(df::unit* unit)
  421. {
  422. // case a: trained for war/hunting (those don't have a training level, strangely)
  423. if(isWar(unit) || isHunter(unit))
  424. return true;
  425. // case b: tamed and trained wild creature, gets a training level
  426. bool trained = false;
  427. switch (unit->training_level)
  428. {
  429. case df::animal_training_level::Trained:
  430. case df::animal_training_level::WellTrained:
  431. case df::animal_training_level::SkilfullyTrained:
  432. case df::animal_training_level::ExpertlyTrained:
  433. case df::animal_training_level::ExceptionallyTrained:
  434. case df::animal_training_level::MasterfullyTrained:
  435. //case df::animal_training_level::Domesticated:
  436. trained = true;
  437. break;
  438. default:
  439. break;
  440. }
  441. return trained;
  442. }
  443. // check for profession "war creature"
  444. bool isWar(df::unit* unit)
  445. {
  446. if( unit->profession == df::profession::TRAINED_WAR
  447. || unit->profession2 == df::profession::TRAINED_WAR)
  448. return true;
  449. else
  450. return false;
  451. }
  452. // check for profession "hunting creature"
  453. bool isHunter(df::unit* unit)
  454. {
  455. if( unit->profession == df::profession::TRAINED_HUNTER
  456. || unit->profession2 == df::profession::TRAINED_HUNTER)
  457. return true;
  458. else
  459. return false;
  460. }
  461. // check if creature belongs to the player's civilization
  462. // (don't try to pasture/slaughter random untame animals)
  463. bool isOwnCiv(df::unit* unit)
  464. {
  465. return unit->civ_id == ui->civ_id;
  466. }
  467. // check if creature belongs to the player's race
  468. // (in combination with check for civ helps to filter out own dwarves)
  469. bool isOwnRace(df::unit* unit)
  470. {
  471. return unit->race == ui->race_id;
  472. }
  473. string getRaceName(int32_t id)
  474. {
  475. df::creature_raw *raw = df::global::world->raws.creatures.all[id];
  476. return raw->creature_id;
  477. }
  478. string getRaceName(df::unit* unit)
  479. {
  480. df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
  481. return raw->creature_id;
  482. }
  483. string getRaceBabyName(df::unit* unit)
  484. {
  485. df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
  486. return raw->general_baby_name[0];
  487. }
  488. string getRaceChildName(df::unit* unit)
  489. {
  490. df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
  491. return raw->general_child_name[0];
  492. }
  493. bool isBaby(df::unit* unit)
  494. {
  495. return unit->profession == df::profession::BABY;
  496. }
  497. bool isChild(df::unit* unit)
  498. {
  499. return unit->profession == df::profession::CHILD;
  500. }
  501. bool isAdult(df::unit* unit)
  502. {
  503. return !isBaby(unit) && !isChild(unit);
  504. }
  505. bool isEggLayer(df::unit* unit)
  506. {
  507. df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
  508. size_t sizecas = raw->caste.size();
  509. for (size_t j = 0; j < sizecas;j++)
  510. {
  511. df::caste_raw *caste = raw->caste[j];
  512. if( caste->flags.is_set(caste_raw_flags::LAYS_EGGS)
  513. || caste->flags.is_set(caste_raw_flags::LAYS_UNUSUAL_EGGS))
  514. return true;
  515. }
  516. return false;
  517. }
  518. bool isGrazer(df::unit* unit)
  519. {
  520. df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
  521. size_t sizecas = raw->caste.size();
  522. for (size_t j = 0; j < sizecas;j++)
  523. {
  524. df::caste_raw *caste = raw->caste[j];
  525. if(caste->flags.is_set(caste_raw_flags::GRAZER))
  526. return true;
  527. }
  528. return false;
  529. }
  530. bool isMilkable(df::unit* unit)
  531. {
  532. df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
  533. size_t sizecas = raw->caste.size();
  534. for (size_t j = 0; j < sizecas;j++)
  535. {
  536. df::caste_raw *caste = raw->caste[j];
  537. if(caste->flags.is_set(caste_raw_flags::MILKABLE))
  538. return true;
  539. }
  540. return false;
  541. }
  542. bool isTrainableWar(df::unit* unit)
  543. {
  544. df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
  545. size_t sizecas = raw->caste.size();
  546. for (size_t j = 0; j < sizecas;j++)
  547. {
  548. df::caste_raw *caste = raw->caste[j];
  549. if(caste->flags.is_set(caste_raw_flags::TRAINABLE_WAR))
  550. return true;
  551. }
  552. return false;
  553. }
  554. bool isTrainableHunting(df::unit* unit)
  555. {
  556. df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
  557. size_t sizecas = raw->caste.size();
  558. for (size_t j = 0; j < sizecas;j++)
  559. {
  560. df::caste_raw *caste = raw->caste[j];
  561. if(caste->flags.is_set(caste_raw_flags::TRAINABLE_HUNTING))
  562. return true;
  563. }
  564. return false;
  565. }
  566. bool isTamable(df::unit* unit)
  567. {
  568. df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race];
  569. size_t sizecas = raw->caste.size();
  570. for (size_t j = 0; j < sizecas;j++)
  571. {
  572. df::caste_raw *caste = raw->caste[j];
  573. if(caste->flags.is_set(caste_raw_flags::PET) ||
  574. caste->flags.is_set(caste_raw_flags::PET_EXOTIC))
  575. return true;
  576. }
  577. return false;
  578. }
  579. bool isMale(df::unit* unit)
  580. {
  581. return unit->sex == 1;
  582. }
  583. bool isFemale(df::unit* unit)
  584. {
  585. return unit->sex == 0;
  586. }
  587. // found a unit with weird position values on one of my maps (negative and in the thousands)
  588. // it didn't appear in the animal stocks screen, but looked completely fine otherwise (alive, tame, own, etc)
  589. // maybe a rare bug, but better avoid assigning such units to zones or slaughter etc.
  590. bool hasValidMapPos(df::unit* unit)
  591. {
  592. if( unit->pos.x >=0 && unit->pos.y >= 0 && unit->pos.z >= 0
  593. && unit->pos.x < world->map.x_count
  594. && unit->pos.y < world->map.y_count
  595. && unit->pos.z < world->map.z_count)
  596. return true;
  597. else
  598. return false;
  599. }
  600. bool isNaked(df::unit* unit)
  601. {
  602. return (unit->inventory.empty());
  603. }
  604. int getUnitIndexFromId(df::unit* unit_)
  605. {
  606. for (size_t i=0; i < world->units.all.size(); i++)
  607. {
  608. df::unit* unit = world->units.all[i];
  609. if(unit->id == unit_->id)
  610. return i;
  611. }
  612. return -1;
  613. }
  614. // dump some unit info
  615. void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false)
  616. {
  617. out.print("Unit %d ", unit->id); //race %d, civ %d,", creature->race, creature->civ_id
  618. if(unit->name.has_name)
  619. {
  620. // units given a nick with the rename tool might not have a first name (animals etc)
  621. string firstname = unit->name.first_name;
  622. if(firstname.size() > 0)
  623. {
  624. firstname[0] = toupper(firstname[0]);
  625. out << "Name: " << firstname;
  626. }
  627. if(unit->name.nickname.size() > 0)
  628. out << " '" << unit->name.nickname << "'";
  629. out << ", ";
  630. }
  631. if(isAdult(unit))
  632. out << "adult";
  633. else if(isBaby(unit))
  634. out << "baby";
  635. else if(isChild(unit))
  636. out << "child";
  637. out << " ";
  638. // sometimes empty even in vanilla RAWS, sometimes contains full race name (e.g. baby alpaca)
  639. // all animals I looked at don't have babies anyways, their offspring starts as CHILD
  640. //out << getRaceBabyName(unit);
  641. //out << getRaceChildName(unit);
  642. out << getRaceName(unit) << " (";
  643. switch(unit->sex)
  644. {
  645. case 0:
  646. out << "female";
  647. break;
  648. case 1:
  649. out << "male";
  650. break;
  651. case -1:
  652. default:
  653. out << "n/a";
  654. break;
  655. }
  656. out << ")";
  657. out << ", age: " << getUnitAge(unit);
  658. if(isTame(unit))
  659. out << ", tame";
  660. if(isOwnCiv(unit))
  661. out << ", owned";
  662. if(isWar(unit))
  663. out << ", war";
  664. if(isHunter(unit))
  665. out << ", hunter";
  666. if(isMerchant(unit))
  667. out << ", merchant";
  668. if(isForest(unit))
  669. out << ", forest";
  670. if(isEggLayer(unit))
  671. out << ", egglayer";
  672. if(isGrazer(unit))
  673. out << ", grazer";
  674. if(isMilkable(unit))
  675. out << ", milkable";
  676. if(verbose)
  677. {
  678. out << ". Pos: ("<<unit->pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ") " << endl;
  679. out << "index in units vector: " << getUnitIndexFromId(unit) << endl;
  680. }
  681. out << endl;
  682. if(!verbose)
  683. return;
  684. //out << "number of refs: " << creature->refs.size() << endl;
  685. for(size_t r = 0; r<unit->refs.size(); r++)
  686. {
  687. df::general_ref* ref = unit->refs.at(r);
  688. df::general_ref_type refType = ref->getType();
  689. out << " ref#" << r <<" refType#" << refType << " "; //endl;
  690. switch(refType)
  691. {
  692. case df::general_ref_type::BUILDING_CIVZONE_ASSIGNED:
  693. {
  694. out << "assigned to zone";
  695. df::building_civzonest * civAss = (df::building_civzonest *) ref->getBuilding();
  696. out << " #" << civAss->id;
  697. }
  698. break;
  699. case df::general_ref_type::CONTAINED_IN_ITEM:
  700. out << "contained in item";
  701. break;
  702. case df::general_ref_type::BUILDING_CAGED:
  703. out << "caged";
  704. break;
  705. case df::general_ref_type::BUILDING_CHAIN:
  706. out << "chained";
  707. break;
  708. default:
  709. //out << "unhandled reftype";
  710. break;
  711. }
  712. out << endl;
  713. }
  714. if(isInBuiltCageRoom(unit))
  715. {
  716. out << "in a room." << endl;
  717. }
  718. }
  719. bool isActivityZone(df::building * building)
  720. {
  721. if( building->getType() == building_type::Civzone
  722. && building->getSubtype() == civzone_type::ActivityZone)
  723. return true;
  724. else
  725. return false;
  726. }
  727. bool isPenPasture(df::building * building)
  728. {
  729. if(!isActivityZone(building))
  730. return false;
  731. df::building_civzonest * civ = (df::building_civzonest *) building;
  732. if(civ->zone_flags.bits.pen_pasture)
  733. return true;
  734. else
  735. return false;
  736. }
  737. bool isPitPond(df::building * building)
  738. {
  739. if(!isActivityZone(building))
  740. return false;
  741. df::building_civzonest * civ = (df::building_civzonest *) building;
  742. if(civ->zone_flags.bits.pit_pond) // && civ->pit_flags==0)
  743. return true;
  744. else
  745. return false;
  746. }
  747. bool isCage(df::building * building)
  748. {
  749. return building->getType() == building_type::Cage;
  750. }
  751. bool isChain(df::building * building)
  752. {
  753. return building->getType() == building_type::Chain;
  754. }
  755. bool isActive(df::building * building)
  756. {
  757. if(!isActivityZone(building))
  758. return false;
  759. df::building_civzonest * civ = (df::building_civzonest *) building;
  760. if(civ->zone_flags.bits.active)
  761. return true;
  762. else
  763. return false;
  764. }
  765. int32_t findBuildingIndexById(int32_t id)
  766. {
  767. for (size_t b = 0; b < world->buildings.all.size(); b++)
  768. {
  769. if(world->buildings.all.at(b)->id == id)
  770. return b;
  771. }
  772. return -1;
  773. }
  774. int32_t findUnitIndexById(int32_t id)
  775. {
  776. for (size_t i = 0; i < world->units.all.size(); i++)
  777. {
  778. if(world->units.all.at(i)->id == id)
  779. return i;
  780. }
  781. return -1;
  782. }
  783. df::unit* findUnitById(int32_t id)
  784. {
  785. int32_t index = findUnitIndexById(id);
  786. if(index != -1)
  787. return world->units.all[index];
  788. else
  789. return NULL;
  790. }
  791. // returns id of pen/pit at cursor position (-1 if nothing found)
  792. int32_t findPenPitAtCursor()
  793. {
  794. int32_t foundID = -1;
  795. if(cursor->x == -30000)
  796. return -1;
  797. for (size_t b = 0; b < world->buildings.all.size(); b++)
  798. {
  799. df::building* building = world->buildings.all[b];
  800. // find zone under cursor
  801. if (!(building->x1 <= cursor->x && cursor->x <= building->x2 &&
  802. building->y1 <= cursor->y && cursor->y <= building->y2 &&
  803. building->z == cursor->z))
  804. continue;
  805. if(isPenPasture(building) || isPitPond(building))
  806. {
  807. foundID = building->id;
  808. break;
  809. }
  810. }
  811. return foundID;
  812. }
  813. // returns id of cage at cursor position (-1 if nothing found)
  814. int32_t findCageAtCursor()
  815. {
  816. int32_t foundID = -1;
  817. if(cursor->x == -30000)
  818. return -1;
  819. for (size_t b = 0; b < world->buildings.all.size(); b++)
  820. {
  821. df::building* building = world->buildings.all[b];
  822. if (!(building->x1 <= cursor->x && cursor->x <= building->x2 &&
  823. building->y1 <= cursor->y && cursor->y <= building->y2 &&
  824. building->z == cursor->z))
  825. continue;
  826. // don't set id if cage is not constructed yet
  827. if(building->getBuildStage()!=building->getMaxBuildStage())
  828. break;
  829. if(isCage(building))
  830. {
  831. foundID = building->id;
  832. break;
  833. }
  834. }
  835. return foundID;
  836. }
  837. int32_t findChainAtCursor()
  838. {
  839. int32_t foundID = -1;
  840. if(cursor->x == -30000)
  841. return -1;
  842. for (size_t b = 0; b < world->buildings.all.size(); b++)
  843. {
  844. df::building* building = world->buildings.all[b];
  845. // find zone under cursor
  846. if (!(building->x1 <= cursor->x && cursor->x <= building->x2 &&
  847. building->y1 <= cursor->y && cursor->y <= building->y2 &&
  848. building->z == cursor->z))
  849. continue;
  850. if(isChain(building))
  851. {
  852. foundID = building->id;
  853. break;
  854. }
  855. }
  856. return foundID;
  857. }
  858. df::general_ref_building_civzone_assignedst * createCivzoneRef()
  859. {
  860. static bool vt_initialized = false;
  861. df::general_ref_building_civzone_assignedst* newref = NULL;
  862. // after having run successfully for the first time it's safe to simply create the object
  863. if(vt_initialized)
  864. {
  865. newref = (df::general_ref_building_civzone_assignedst*)
  866. df::general_ref_building_civzone_assignedst::_identity.instantiate();
  867. return newref;
  868. }
  869. // being called for the first time, need to initialize the vtable
  870. for(size_t i = 0; i < world->units.all.size(); i++)
  871. {
  872. df::unit * creature = world->units.all[i];
  873. for(size_t r = 0; r<creature->refs.size(); r++)
  874. {
  875. df::general_ref* ref;
  876. ref = creature->refs.at(r);
  877. if(ref->getType() == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED)
  878. {
  879. if (strict_virtual_cast<df::general_ref_building_civzone_assignedst>(ref))
  880. {
  881. // !! calling new() doesn't work, need _identity.instantiate() instead !!
  882. newref = (df::general_ref_building_civzone_assignedst*)
  883. df::general_ref_building_civzone_assignedst::_identity.instantiate();
  884. vt_initialized = true;
  885. break;
  886. }
  887. }
  888. }
  889. if(vt_initialized)
  890. break;
  891. }
  892. return newref;
  893. }
  894. bool isInBuiltCage(df::unit* unit);
  895. // check if assigned to pen, pit, (built) cage or chain
  896. // note: BUILDING_CAGED is not set for animals (maybe it's used for dwarves who get caged as sentence)
  897. // animals in cages (no matter if built or on stockpile) get the ref CONTAINED_IN_ITEM instead
  898. // removing them from cages on stockpiles is no problem even without clearing the ref
  899. // and usually it will be desired behavior to do so.
  900. bool isAssigned(df::unit* unit)
  901. {
  902. bool assigned = false;
  903. for (size_t r=0; r < unit->refs.size(); r++)
  904. {
  905. df::general_ref * ref = unit->refs[r];
  906. auto rtype = ref->getType();
  907. if( rtype == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED
  908. || rtype == df::general_ref_type::BUILDING_CAGED
  909. || rtype == df::general_ref_type::BUILDING_CHAIN
  910. || (rtype == df::general_ref_type::CONTAINED_IN_ITEM && isInBuiltCage(unit))
  911. )
  912. {
  913. assigned = true;
  914. break;
  915. }
  916. }
  917. return assigned;
  918. }
  919. // check if assigned to a chain or built cage
  920. // (need to check if the ref needs to be removed, until then touching them is forbidden)
  921. bool isChained(df::unit* unit)
  922. {
  923. bool contained = false;
  924. for (size_t r=0; r < unit->refs.size(); r++)
  925. {
  926. df::general_ref * ref = unit->refs[r];
  927. auto rtype = ref->getType();
  928. if(rtype == df::general_ref_type::BUILDING_CHAIN)
  929. {
  930. contained = true;
  931. break;
  932. }
  933. }
  934. return contained;
  935. }
  936. // check if contained in item (e.g. animals in cages)
  937. bool isContainedInItem(df::unit* unit)
  938. {
  939. bool contained = false;
  940. for (size_t r=0; r < unit->refs.size(); r++)
  941. {
  942. df::general_ref * ref = unit->refs[r];
  943. auto rtype = ref->getType();
  944. if(rtype == df::general_ref_type::CONTAINED_IN_ITEM)
  945. {
  946. contained = true;
  947. break;
  948. }
  949. }
  950. return contained;
  951. }
  952. bool isInBuiltCage(df::unit* unit)
  953. {
  954. bool caged = false;
  955. for (size_t b=0; b < world->buildings.all.size(); b++)
  956. {
  957. df::building* building = world->buildings.all[b];
  958. if( building->getType() == building_type::Cage)
  959. {
  960. df::building_cagest* cage = (df::building_cagest*) building;
  961. for(size_t c=0; c<cage->assigned_creature.size(); c++)
  962. {
  963. if(cage->assigned_creature[c] == unit->id)
  964. {
  965. caged = true;
  966. break;
  967. }
  968. }
  969. }
  970. if(caged)
  971. break;
  972. }
  973. return caged;
  974. }
  975. // built cage defined as room (supposed to detect zoo cages)
  976. bool isInBuiltCageRoom(df::unit* unit)
  977. {
  978. bool caged_room = false;
  979. for (size_t b=0; b < world->buildings.all.size(); b++)
  980. {
  981. df::building* building = world->buildings.all[b];
  982. // !!! building->isRoom() returns true if the building can be made a room but currently isn't
  983. // !!! except for coffins/tombs which always return false
  984. // !!! using the bool is_room however gives the correct state/value
  985. if(!building->is_room)
  986. continue;
  987. if(building->getType() == building_type::Cage)
  988. {
  989. df::building_cagest* cage = (df::building_cagest*) building;
  990. for(size_t c=0; c<cage->assigned_creature.size(); c++)
  991. {
  992. if(cage->assigned_creature[c] == unit->id)
  993. {
  994. caged_room = true;
  995. break;
  996. }
  997. }
  998. }
  999. if(caged_room)
  1000. break;
  1001. }
  1002. return caged_room;
  1003. }
  1004. // check a map position for a built cage
  1005. // animals in cages are CONTAINED_IN_ITEM, no matter if they are on a stockpile or inside a built cage
  1006. // if they are on animal stockpiles they should count as unassigned to allow pasturing them
  1007. // if they are inside built cages they should be ignored in case the cage is a zoo or linked to a lever or whatever
  1008. bool isBuiltCageAtPos(df::coord pos)
  1009. {
  1010. bool cage = false;
  1011. for (size_t b=0; b < world->buildings.all.size(); b++)
  1012. {
  1013. df::building* building = world->buildings.all[b];
  1014. if( building->getType() == building_type::Cage
  1015. && building->x1 == pos.x
  1016. && building->y1 == pos.y
  1017. && building->z == pos.z )
  1018. {
  1019. cage = true;
  1020. break;
  1021. }
  1022. }
  1023. return cage;
  1024. }
  1025. df::building * getBuiltCageAtPos(df::coord pos)
  1026. {
  1027. df::building* cage = NULL;
  1028. for (size_t b=0; b < world->buildings.all.size(); b++)
  1029. {
  1030. df::building* building = world->buildings.all[b];
  1031. if( building->getType() == building_type::Cage
  1032. && building->x1 == pos.x
  1033. && building->y1 == pos.y
  1034. && building->z == pos.z )
  1035. {
  1036. // don't set pointer if not constructed yet
  1037. if(building->getBuildStage()!=building->getMaxBuildStage())
  1038. break;
  1039. cage = building;
  1040. break;
  1041. }
  1042. }
  1043. return cage;
  1044. }
  1045. bool isNestboxAtPos(int32_t x, int32_t y, int32_t z)
  1046. {
  1047. bool found = false;
  1048. for (size_t b=0; b < world->buildings.all.size(); b++)
  1049. {
  1050. df::building* building = world->buildings.all[b];
  1051. if( building->getType() == building_type::NestBox
  1052. && building->x1 == x
  1053. && building->y1 == y
  1054. && building->z == z )
  1055. {
  1056. found = true;
  1057. break;
  1058. }
  1059. }
  1060. return found;
  1061. }
  1062. bool isFreeNestboxAtPos(int32_t x, int32_t y, int32_t z)
  1063. {
  1064. bool found = false;
  1065. for (size_t b=0; b < world->buildings.all.size(); b++)
  1066. {
  1067. df::building* building = world->buildings.all[b];
  1068. if( building->getType() == building_type::NestBox
  1069. && building->x1 == x
  1070. && building->y1 == y
  1071. && building->z == z )
  1072. {
  1073. df::building_nest_boxst* nestbox = (df::building_nest_boxst*) building;
  1074. if(nestbox->claimed_by == -1 && nestbox->contained_items.size() == 1)
  1075. {
  1076. found = true;
  1077. break;
  1078. }
  1079. }
  1080. }
  1081. return found;
  1082. }
  1083. bool isEmptyPasture(df::building* building)
  1084. {
  1085. if(!isPenPasture(building))
  1086. return false;
  1087. df::building_civzonest * civ = (df::building_civzonest *) building;
  1088. if(civ->assigned_creature.size() == 0)
  1089. return true;
  1090. else
  1091. return false;
  1092. }
  1093. df::building* findFreeNestboxZone()
  1094. {
  1095. df::building * free_building = NULL;
  1096. bool cage = false;
  1097. for (size_t b=0; b < world->buildings.all.size(); b++)
  1098. {
  1099. df::building* building = world->buildings.all[b];
  1100. if( isEmptyPasture(building) &&
  1101. isActive(building) &&
  1102. isFreeNestboxAtPos(building->x1, building->y1, building->z))
  1103. {
  1104. free_building = building;
  1105. break;
  1106. }
  1107. }
  1108. return free_building;
  1109. }
  1110. bool isFreeEgglayer(df::unit * unit)
  1111. {
  1112. if( !isDead(unit) && !isUndead(unit)
  1113. && isFemale(unit)
  1114. && isTame(unit)
  1115. && isOwnCiv(unit)
  1116. && isEggLayer(unit)
  1117. && !isAssigned(unit)
  1118. && !isGrazer(unit) // exclude grazing birds because they're messy
  1119. && !isMerchant(unit) // don't steal merchant mounts
  1120. && !isForest(unit) // don't steal birds from traders, they hate that
  1121. )
  1122. return true;
  1123. else
  1124. return false;
  1125. }
  1126. df::unit * findFreeEgglayer()
  1127. {
  1128. df::unit* free_unit = NULL;
  1129. for (size_t i=0; i < world->units.all.size(); i++)
  1130. {
  1131. df::unit* unit = world->units.all[i];
  1132. if(isFreeEgglayer(unit))
  1133. {
  1134. free_unit = unit;
  1135. break;
  1136. }
  1137. }
  1138. return free_unit;
  1139. }
  1140. size_t countFreeEgglayers()
  1141. {
  1142. size_t count = 0;
  1143. for (size_t i=0; i < world->units.all.size(); i++)
  1144. {
  1145. df::unit* unit = world->units.all[i];
  1146. if(isFreeEgglayer(unit))
  1147. count ++;
  1148. }
  1149. return count;
  1150. }
  1151. // check if unit is already assigned to a zone, remove that ref from unit and old zone
  1152. // check if unit is already assigned to a cage, remove that ref from the cage
  1153. // returns false if no cage or pasture information was found
  1154. // helps as workaround for http://www.bay12games.com/dwarves/mantisbt/view.php?id=4475 by the way
  1155. // (pastured animals assigned to chains will get hauled back and forth because the pasture ref is not deleted)
  1156. bool unassignUnitFromBuilding(df::unit* unit)
  1157. {
  1158. bool success = false;
  1159. for (std::size_t idx = 0; idx < unit->refs.size(); idx++)
  1160. {
  1161. df::general_ref * oldref = unit->refs[idx];
  1162. switch(oldref->getType())
  1163. {
  1164. case df::general_ref_type::BUILDING_CIVZONE_ASSIGNED:
  1165. {
  1166. unit->refs.erase(unit->refs.begin() + idx);
  1167. df::building_civzonest * oldciv = (df::building_civzonest *) oldref->getBuilding();
  1168. for(size_t oc=0; oc<oldciv->assigned_creature.size(); oc++)
  1169. {
  1170. if(oldciv->assigned_creature[oc] == unit->id)
  1171. {
  1172. oldciv->assigned_creature.erase(oldciv->assigned_creature.begin() + oc);
  1173. break;
  1174. }
  1175. }
  1176. delete oldref;
  1177. success = true;
  1178. break;
  1179. }
  1180. case df::general_ref_type::CONTAINED_IN_ITEM:
  1181. {
  1182. // game does not erase the ref until creature gets removed from cage
  1183. //unit->refs.erase(unit->refs.begin() + idx);
  1184. // walk through buildings, check cages for inhabitants, compare ids
  1185. for (size_t b=0; b < world->buildings.all.size(); b++)
  1186. {
  1187. bool found = false;
  1188. df::building* building = world->buildings.all[b];
  1189. if(isCage(building))
  1190. {
  1191. df::building_cagest* oldcage = (df::building_cagest*) building;
  1192. for(size_t oc=0; oc<oldcage->assigned_creature.size(); oc++)
  1193. {
  1194. if(oldcage->assigned_creature[oc] == unit->id)
  1195. {
  1196. oldcage->assigned_creature.erase(oldcage->assigned_creature.begin() + oc);
  1197. found = true;
  1198. break;
  1199. }
  1200. }
  1201. }
  1202. if(found)
  1203. break;
  1204. }
  1205. success = true;
  1206. break;
  1207. }
  1208. case df::general_ref_type::BUILDING_CHAIN:
  1209. {
  1210. // try not erasing the ref and see what happens
  1211. //unit->refs.erase(unit->refs.begin() + idx);
  1212. // probably need to delete chain reference here
  1213. //success = true;
  1214. break;
  1215. }
  1216. case df::general_ref_type::BUILDING_CAGED:
  1217. {
  1218. // not sure what to do here, doesn't seem to get used by the game
  1219. //unit->refs.erase(unit->refs.begin() + idx);
  1220. //success = true;
  1221. break;
  1222. }
  1223. default:
  1224. {
  1225. // some reference which probably shouldn't get deleted
  1226. // (animals who are historical figures and have a NEMESIS reference or whatever)
  1227. break;
  1228. }
  1229. }
  1230. }
  1231. return success;
  1232. }
  1233. // assign to pen or pit
  1234. command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false)
  1235. {
  1236. // building must be a pen/pasture or pit
  1237. if(!isPenPasture(building) && !isPitPond(building))
  1238. {
  1239. out << "Invalid building type. This is not a pen/pasture or pit/pond." << endl;
  1240. return CR_WRONG_USAGE;
  1241. }
  1242. // try to get a fresh civzone ref
  1243. df::general_ref_building_civzone_assignedst * ref = createCivzoneRef();
  1244. if(!ref)
  1245. {
  1246. out << "Could not find a clonable activity zone reference" << endl
  1247. << "You need to pen/pasture/pit at least one creature" << endl
  1248. << "before using 'assign' for the first time." << endl;
  1249. return CR_WRONG_USAGE;
  1250. }
  1251. // check if unit is already pastured, remove that ref from unit and old pasture
  1252. // testing showed that removing the ref from the unit only seems to be necessary for pastured creatures
  1253. // if they are in cages on stockpiles the game unassigns them automatically
  1254. // if they are in built cages the pointer to the creature needs to be removed from the cage
  1255. // TODO: check what needs to be done for chains
  1256. bool cleared_old = unassignUnitFromBuilding(unit);
  1257. if(verbose)
  1258. {
  1259. if(cleared_old)
  1260. out << "old zone info cleared.";
  1261. else
  1262. out << "no old zone info found.";
  1263. }
  1264. ref->building_id = building->id;
  1265. unit->refs.push_back(ref);
  1266. df::building_civzonest * civz = (df::building_civzonest *) building;
  1267. civz->assigned_creature.push_back(unit->id);
  1268. out << "Unit " << unit->id
  1269. << "(" << getRaceName(unit) << ")"
  1270. << " assigned to zone " << building->id;
  1271. if(isPitPond(building))
  1272. out << " (pit/pond).";
  1273. if(isPenPasture(building))
  1274. out << " (pen/pasture).";
  1275. out << endl;
  1276. return CR_OK;
  1277. }
  1278. command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building* building, bool verbose)
  1279. {
  1280. // building must be a pen/pasture or pit
  1281. if(!isCage(building))
  1282. {
  1283. out << "Invalid building type. This is not a cage." << endl;
  1284. return CR_WRONG_USAGE;
  1285. }
  1286. // don't assign owned pets to a cage. the owner will release them, resulting into infinite hauling (df bug)
  1287. if(unit->relations.pet_owner_id != -1)
  1288. return CR_OK;
  1289. // check if unit is already pastured or caged, remove refs where necessary
  1290. bool cleared_old = unassignUnitFromBuilding(unit);
  1291. if(verbose)
  1292. {
  1293. if(cleared_old)
  1294. out << "old zone info cleared.";
  1295. else
  1296. out << "no old zone info found.";
  1297. }
  1298. //ref->building_id = building->id;
  1299. //unit->refs.push_back(ref);
  1300. df::building_cagest* civz = (df::building_cagest*) building;
  1301. civz->assigned_creature.push_back(unit->id);
  1302. out << "Unit " << unit->id
  1303. << "(" << getRaceName(unit) << ")"
  1304. << " assigned to cage " << building->id;
  1305. out << endl;
  1306. return CR_OK;
  1307. }
  1308. command_result assignUnitToChain(color_ostream& out, df::unit* unit, df::building* building, bool verbose)
  1309. {
  1310. out << "sorry. assigning to chains is not possible yet." << endl;
  1311. return CR_WRONG_USAGE;
  1312. }
  1313. command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::building* building, bool verbose)
  1314. {
  1315. command_result result = CR_WRONG_USAGE;
  1316. if(isActivityZone(building))
  1317. result = assignUnitToZone(out, unit, building, verbose);
  1318. else if(isCage(building))
  1319. result = assignUnitToCage(out, unit, building, verbose);
  1320. else if(isChain(building))
  1321. result = assignUnitToChain(out, unit, building, verbose);
  1322. else
  1323. out << "Cannot assign units to this type of building!" << endl;
  1324. return result;
  1325. }
  1326. command_result assignUnitsToCagezone(color_ostream& out, vector<df::unit*> units, df::building* building, bool verbose)
  1327. {
  1328. command_result result = CR_WRONG_USAGE;
  1329. if(!isPenPasture(building))
  1330. {
  1331. out << "A cage zone needs to be a pen/pasture containing at least one cage!" << endl;
  1332. return CR_WRONG_USAGE;
  1333. }
  1334. int32_t x1 = building->x1;
  1335. int32_t x2 = building->x2;
  1336. int32_t y1 = building->y1;
  1337. int32_t y2 = building->y2;
  1338. int32_t z = building->z;
  1339. vector <df::building_cagest*> cages;
  1340. for (int32_t x = x1; x<=x2; x++)
  1341. {
  1342. for (int32_t y = y1; y<=y2; y++)
  1343. {
  1344. df::building* cage = getBuiltCageAtPos(df::coord(x,y,z));
  1345. if(cage)
  1346. {
  1347. df::building_cagest* cagest = (df::building_cagest*) cage;
  1348. cages.push_back(cagest);
  1349. }
  1350. }
  1351. }
  1352. if(!cages.size())
  1353. {
  1354. out << "No cages found in this zone!" << endl;
  1355. return CR_WRONG_USAGE;
  1356. }
  1357. else
  1358. {
  1359. out << "Number of cages: " << cages.size() << endl;
  1360. }
  1361. while(units.size())
  1362. {
  1363. // hrm, better use sort() instead?
  1364. df::building_cagest * bestcage = cages[0];
  1365. size_t lowest = cages[0]->assigned_creature.size();
  1366. for(size_t i=1; i<cages.size(); i++)
  1367. {
  1368. if(cages[i]->assigned_creature.size()<lowest)
  1369. {
  1370. lowe

Large files files are truncated, but you can click here to view the full file