diff --git a/NEWS b/NEWS index 5103cf921..c8a0f5770 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,7 @@ DFHack future - removebadthoughts: add --dry-run option New tweaks: - tweak armory: prevents stockpiling of armor and weapons stored on stands and racks. + - tweak stock-armory: makes dwarves actively haul equipment from stockpiles to the armory. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/dfhack.init-example b/dfhack.init-example index 7dfa86016..2b7ac2cc9 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -124,3 +124,4 @@ tweak military-color-assigned # stop squad equpment stored on weapon racks and armor stands from being stockpiled tweak armory +tweak stock-armory diff --git a/library/xml b/library/xml index 4a6903dc9..4bfc5b197 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 4a6903dc9b8d4cd21c5c5d74df4e9f74a4dd58dd +Subproject commit 4bfc5b197de9ca096a11b9c1594e043bd8ae707a diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index dd504fee3..9dc3ae468 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -10,6 +10,7 @@ #include "modules/Screen.h" #include "modules/Units.h" #include "modules/Items.h" +#include "modules/Job.h" #include "MiscUtils.h" @@ -58,7 +59,12 @@ #include "df/item_flaskst.h" #include "df/item_backpackst.h" #include "df/item_quiverst.h" +#include "df/building_weaponrackst.h" #include "df/building_armorstandst.h" +#include "df/building_cabinetst.h" +#include "df/building_boxst.h" +#include "df/job.h" +#include "df/general_ref_building_holderst.h" #include @@ -139,6 +145,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector getSpecificSquad(); if (sid < 0) @@ -683,7 +691,9 @@ static bool belongs_to_position(df::item *item, df::building *holder, bool any_p if (!squad) return false; - if (any_position) + int position = holder->getSpecificPosition(); + + if (position == -1 && holder->getType() == building_type::Weaponrack) { for (size_t i = 0; i < squad->positions.size(); i++) { @@ -693,7 +703,7 @@ static bool belongs_to_position(df::item *item, df::building *holder, bool any_p } else { - auto cpos = vector_get(squad->positions, holder->getSpecificPosition()); + auto cpos = vector_get(squad->positions, position); if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0) return true; } @@ -701,7 +711,7 @@ static bool belongs_to_position(df::item *item, df::building *holder, bool any_p return false; } -static bool is_in_armory(df::item *item, bool any_position) +static bool is_in_armory(df::item *item) { if (item->flags.bits.in_inventory || item->flags.bits.on_ground) return false; @@ -714,35 +724,22 @@ static bool is_in_armory(df::item *item, bool any_position) if (!holder) return false; - return belongs_to_position(item, holder, any_position); + return belongs_to_position(item, holder); } -struct armory_weapon_hook : df::item_weaponst { - typedef df::item_weaponst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) - { - if (is_in_armory(this, true)) - return false; - - return INTERPOSE_NEXT(isCollected)(); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(armory_weapon_hook, isCollected); - template struct armory_hook : Item { typedef Item interpose_base; DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { - if (is_in_armory(this, false)) + if (is_in_armory(this)) return false; return INTERPOSE_NEXT(isCollected)(); } }; +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); @@ -753,6 +750,112 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollecte template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +static void check_stock_armory(df::building *target, int squad_idx) +{ + auto squad = df::squad::find(squad_idx); + if (!squad) + return; + + int position = target->getSpecificPosition(); + + for (size_t i = 0; i < squad->positions.size(); i++) + { + if (position >= 0 && position != int(i)) + continue; + + auto pos = squad->positions[i]; + + for (size_t j = 0; j < pos->assigned_items.size(); j++) + { + auto item = df::item::find(pos->assigned_items[j]); + if (!item || item->stockpile_countdown > 0) + continue; + + if (item->flags.bits.in_job || + item->flags.bits.removed || + item->flags.bits.in_building || + item->flags.bits.encased || + item->flags.bits.owned || + item->flags.bits.forbid || + item->flags.bits.on_fire) + continue; + + auto top = item; + + while (top->flags.bits.in_inventory) + { + auto parent = Items::getContainer(top); + if (!parent) break; + top = parent; + } + + if (Items::getGeneralRef(top, general_ref_type::UNIT_HOLDER)) + continue; + + if (is_in_armory(item)) + continue; + + if (!target->canStoreItem(item, true)) + continue; + + auto href = df::allocate(); + if (!href) + continue; + + auto job = new df::job(); + + job->pos = df::coord(target->centerx, target->centery, target->z); + + switch (target->getType()) { + case building_type::Weaponrack: + job->job_type = job_type::StoreWeapon; + job->flags.bits.specific_dropoff = true; + break; + case building_type::Armorstand: + job->job_type = job_type::StoreArmor; + job->flags.bits.specific_dropoff = true; + break; + case building_type::Cabinet: + job->job_type = job_type::StoreItemInCabinet; + break; + default: + job->job_type = job_type::StoreItemInChest; + break; + } + + if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) + { + delete job; + delete href; + continue; + } + + href->building_id = target->id; + target->jobs.push_back(job); + job->references.push_back(href); + + Job::linkIntoWorld(job); + } + } +} + +template struct stock_armory_hook : Building { + typedef Building interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) + { + if (this->specific_squad >= 0 && DF_GLOBAL_VALUE(cur_year_tick,0) % 50 == 0) + check_stock_armory(this, this->specific_squad); + + INTERPOSE_NEXT(updateAction)(); + } +}; + +template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); + static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -928,7 +1031,7 @@ static command_result tweak(color_ostream &out, vector ¶meters) } else if (cmd == "armory") { - enable_hook(out, INTERPOSE_HOOK(armory_weapon_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); @@ -939,6 +1042,13 @@ static command_result tweak(color_ostream &out, vector ¶meters) enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); } + else if (cmd == "stock-armory") + { + enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); + enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); + enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); + enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); + } else return CR_WRONG_USAGE;