diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 06adbbe4c..6bfdfe421 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -11,6 +11,7 @@ #include "modules/Items.h" #include "modules/Job.h" #include "modules/World.h" +#include "modules/Maps.h" #include "MiscUtils.h" @@ -21,6 +22,7 @@ #include "df/squad.h" #include "df/unit.h" #include "df/squad_position.h" +#include "df/items_other_id.h" #include "df/item_weaponst.h" #include "df/item_armorst.h" #include "df/item_helmst.h" @@ -84,6 +86,32 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) return CR_OK; } +// Check if the item is assigned to any use controlled by the military tab +static bool is_assigned_item(df::item *item) +{ + if (!ui) + return false; + + auto type = item->getType(); + int idx = binsearch_index(ui->equipment.items_assigned[type], item->id); + if (idx < 0) + return false; + + // Exclude weapons used by miners, wood cutters etc + switch (type) { + case item_type::WEAPON: + if (binsearch_index(ui->equipment.work_weapons, item->id) >= 0) + return false; + break; + + default: + break; + } + + return true; +} + +// Check if the item is assigned to the squad member who owns this armory building static bool belongs_to_position(df::item *item, df::building *holder) { int sid = holder->getSpecificSquad(); @@ -96,6 +124,7 @@ static bool belongs_to_position(df::item *item, df::building *holder) int position = holder->getSpecificPosition(); + // Weapon racks belong to the whole squad, i.e. can be used by any position if (position == -1 && holder->getType() == building_type::Weaponrack) { for (size_t i = 0; i < squad->positions.size(); i++) @@ -114,19 +143,17 @@ static bool belongs_to_position(df::item *item, df::building *holder) return false; } +// Check if the item is appropriately stored in an armory building static bool is_in_armory(df::item *item) { if (item->flags.bits.in_inventory || item->flags.bits.on_ground) return false; - auto holder_ref = Items::getGeneralRef(item, general_ref_type::BUILDING_HOLDER); - if (!holder_ref) - return false; - - auto holder = holder_ref->getBuilding(); + auto holder = Items::getHolderBuilding(item); if (!holder) return false; + // If indeed in a building, check if it is the right one return belongs_to_position(item, holder); } @@ -135,11 +162,47 @@ template struct armory_hook : Item { DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { + // Normally this vmethod is used to prevent stockpiling of uncollected webs. + // This uses it to also block stockpiling of items in the armory. if (is_in_armory(this)) return false; + // Also never let items in process of being removed from uniform be stockpiled at once + if (this->flags.bits.in_inventory) + { + auto holder = Items::getHolderUnit(this); + + if (holder && binsearch_index(holder->military.uniform_drop, this->id) >= 0) + { + if (is_assigned_item(this)) + return false; + } + } + return INTERPOSE_NEXT(isCollected)(); } + + DEFINE_VMETHOD_INTERPOSE(bool, moveToGround, (int16_t x, int16_t y, int16_t z)) + { + bool rv = INTERPOSE_NEXT(moveToGround)(x, y, z); + + // Prevent instant restockpiling of dropped assigned items. + if (is_assigned_item(this)) + { + // The original vmethod adds the item to this vector to force instant check + auto &ovec = world->items.other[items_other_id::ANY_RECENTLY_DROPPED]; + + // If it is indeed there, remove it + if (erase_from_vector(ovec, &df::item::id, this->id)) + { + // and queue it to be checked normally in 0.5-1 in-game days + this->stockpile_countdown = 12 + random_int(12); + this->stockpile_delay = 0; + } + } + + return rv; + } }; template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); @@ -153,11 +216,20 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollecte template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); + +// Check if this item is loose and can be moved to armory static bool can_store_item(df::item *item) { - if (!item || item->stockpile_countdown > 0) - return false; - if (item->flags.bits.in_job || item->flags.bits.removed || item->flags.bits.in_building || @@ -185,6 +257,69 @@ static bool can_store_item(df::item *item) return true; } +// Queue a job to store the item in the building, if possible +static bool try_store_item(df::building *target, df::item *item) +{ + // Check if the dwarves can path between the target and the item + df::coord tpos(target->centerx, target->centery, target->z); + df::coord ipos = Items::getPosition(item); + + if (!Maps::canWalkBetween(tpos, ipos)) + return false; + + // Check if the target has enough space left + if (!target->canStoreItem(item, true)) + return false; + + // Create the job + auto href = df::allocate(); + if (!href) + return false; + + auto job = new df::job(); + + job->pos = tpos; + + // Choose the job type - correct matching is needed so that + // later canStoreItem calls would take the job into account. + switch (target->getType()) { + case building_type::Weaponrack: + job->job_type = job_type::StoreWeapon; + // Without this flag dwarves will pick up the item, and + // then dismiss the job and put it back into the stockpile: + 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; + } + + // job <-> item link + if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) + { + delete job; + delete href; + return false; + } + + // job <-> building link + href->building_id = target->id; + target->jobs.push_back(job); + job->references.push_back(href); + + // add to job list + Job::linkIntoWorld(job); + return true; +} + +// Store the item into the first building in the list that would accept it. static void try_store_item(std::vector &vec, df::item *item) { for (size_t i = 0; i < vec.size(); i++) @@ -193,56 +328,23 @@ static void try_store_item(std::vector &vec, df::item *item) if (!target) continue; - if (!target->canStoreItem(item, true)) - continue; - - auto href = df::allocate(); - if (!href) + if (try_store_item(target, item)) return; - - 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); - return; } } +// Store the items into appropriate armory buildings static void try_store_item_set(std::vector &items, df::squad *squad, df::squad_position *pos) { for (size_t j = 0; j < items.size(); j++) { auto item = df::item::find(items[j]); + // bad id, or cooldown timer still counting + if (!item || item->stockpile_countdown > 0) + continue; + + // not loose if (!can_store_item(item)) continue; @@ -264,9 +366,11 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_ev if (!is_enabled) return CR_OK; + // Process every 50th frame, sort of like regular stockpiling does if (DF_GLOBAL_VALUE(cur_year_tick,1) % 50 != 0) return CR_OK; + // Loop over squads auto &squads = df::global::world->squads.all; for (size_t si = 0; si < squads.size(); si++) @@ -304,6 +408,17 @@ static void enable_hooks(color_ostream &out, bool enable) enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); } static void enable_plugin(color_ostream &out)