diff --git a/NEWS b/NEWS index c8a0f5770..810f0cd55 100644 --- a/NEWS +++ b/NEWS @@ -10,9 +10,8 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - 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 commands: + - fix-armory: activates a plugin that makes armor stands and weapon racks be used again. 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 2b7ac2cc9..d7f3f5399 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -121,7 +121,3 @@ tweak fast-trade tweak military-stable-assign # in same list, color units already assigned to squads in brown & green 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/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 2f663f805..de1aa3441 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -124,6 +124,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(add-spatter add-spatter.cpp) + DFHACK_PLUGIN(fix-armory fix-armory.cpp) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp new file mode 100644 index 000000000..06adbbe4c --- /dev/null +++ b/plugins/fix-armory.cpp @@ -0,0 +1,376 @@ +// Fixes containers in barracks to actually work as intended. + +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/Gui.h" +#include "modules/Screen.h" +#include "modules/Units.h" +#include "modules/Items.h" +#include "modules/Job.h" +#include "modules/World.h" + +#include "MiscUtils.h" + +#include "DataDefs.h" +#include +#include "df/ui.h" +#include "df/world.h" +#include "df/squad.h" +#include "df/unit.h" +#include "df/squad_position.h" +#include "df/item_weaponst.h" +#include "df/item_armorst.h" +#include "df/item_helmst.h" +#include "df/item_pantsst.h" +#include "df/item_shoesst.h" +#include "df/item_glovesst.h" +#include "df/item_shieldst.h" +#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 "df/barrack_preference_category.h" + +#include + +using std::vector; +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; + +using df::global::ui; +using df::global::world; +using df::global::gamemode; +using df::global::ui_build_selector; +using df::global::ui_menu_width; +using df::global::ui_area_map_width; + +using namespace DFHack::Gui; +using Screen::Pen; + +static command_result fix_armory(color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("fix-armory"); + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "fix-armory", "Enables or disables the fix-armory plugin.", fix_armory, false, + " fix-armory enable\n" + " Enables the tweaks.\n" + " fix-armory disable\n" + " Disables the tweaks. All equipment will be hauled off to stockpiles.\n" + )); + + if (Core::getInstance().isMapLoaded()) + plugin_onstatechange(out, SC_MAP_LOADED); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + return CR_OK; +} + +static bool belongs_to_position(df::item *item, df::building *holder) +{ + int sid = holder->getSpecificSquad(); + if (sid < 0) + return false; + + auto squad = df::squad::find(sid); + if (!squad) + return false; + + int position = holder->getSpecificPosition(); + + if (position == -1 && holder->getType() == building_type::Weaponrack) + { + for (size_t i = 0; i < squad->positions.size(); i++) + { + if (binsearch_index(squad->positions[i]->assigned_items, item->id) >= 0) + return true; + } + } + else + { + auto cpos = vector_get(squad->positions, position); + if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0) + return true; + } + + return false; +} + +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(); + if (!holder) + return false; + + return belongs_to_position(item, holder); +} + +template struct armory_hook : Item { + typedef Item interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) + { + 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); +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); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); + +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 || + item->flags.bits.encased || + item->flags.bits.owned || + item->flags.bits.forbid || + item->flags.bits.on_fire) + return false; + + 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)) + return false; + + if (is_in_armory(item)) + return false; + + return true; +} + +static void try_store_item(std::vector &vec, df::item *item) +{ + for (size_t i = 0; i < vec.size(); i++) + { + auto target = df::building::find(vec[i]); + if (!target) + continue; + + if (!target->canStoreItem(item, true)) + continue; + + auto href = df::allocate(); + if (!href) + 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; + } +} + +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]); + + if (!can_store_item(item)) + continue; + + if (item->isWeapon()) + try_store_item(squad->rack_combat, item); + else if (item->isClothing()) + try_store_item(pos->preferences[barrack_preference_category::Cabinet], item); + else if (item->isArmorNotClothing()) + try_store_item(pos->preferences[barrack_preference_category::Armorstand], item); + else + try_store_item(pos->preferences[barrack_preference_category::Box], item); + } +} + +static bool is_enabled = false; + +DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event) +{ + if (!is_enabled) + return CR_OK; + + if (DF_GLOBAL_VALUE(cur_year_tick,1) % 50 != 0) + return CR_OK; + + auto &squads = df::global::world->squads.all; + + for (size_t si = 0; si < squads.size(); si++) + { + auto squad = squads[si]; + + for (size_t i = 0; i < squad->positions.size(); i++) + { + auto pos = squad->positions[i]; + + try_store_item_set(pos->assigned_items, squad, pos); + } + } + + return CR_OK; +} + +static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, bool enable) +{ + if (!hook.apply(enable)) + out.printerr("Could not %s hook.\n", enable?"activate":"deactivate"); +} + +static void enable_hooks(color_ostream &out, bool enable) +{ + is_enabled = 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, 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, 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, isCollected), enable); +} + +static void enable_plugin(color_ostream &out) +{ + auto entry = World::GetPersistentData("fix-armory/enabled", NULL); + if (!entry.isValid()) + { + out.printerr("Could not save the status.\n"); + return; + } + + enable_hooks(out, true); +} + +static void disable_plugin(color_ostream &out) +{ + auto entry = World::GetPersistentData("fix-armory/enabled"); + World::DeletePersistentData(entry); + + enable_hooks(out, false); +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + if (!gamemode || *gamemode == game_mode::DWARF) + { + bool enable = World::GetPersistentData("fix-armory/enabled").isValid(); + + if (enable) + { + out.print("Enabling the fix-armory plugin.\n"); + enable_hooks(out, true); + } + else + enable_hooks(out, false); + } + break; + case SC_MAP_UNLOADED: + enable_hooks(out, false); + default: + break; + } + + return CR_OK; +} + +static command_result fix_armory(color_ostream &out, vector ¶meters) +{ + CoreSuspender suspend; + + if (parameters.empty()) + return CR_WRONG_USAGE; + + string cmd = parameters[0]; + + if (cmd == "enable") + { + enable_plugin(out); + } + else if (cmd == "disable") + { + disable_plugin(out); + } + else + return CR_WRONG_USAGE; + + return CR_OK; +} diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 9dc3ae468..93e5ab3bc 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -49,20 +49,6 @@ #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_layer_militaryst.h" #include "df/squad_position.h" -#include "df/item_weaponst.h" -#include "df/item_armorst.h" -#include "df/item_helmst.h" -#include "df/item_pantsst.h" -#include "df/item_shoesst.h" -#include "df/item_glovesst.h" -#include "df/item_shieldst.h" -#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" @@ -142,11 +128,6 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector getSpecificSquad(); - if (sid < 0) - return false; - - auto squad = df::squad::find(sid); - if (!squad) - return false; - - int position = holder->getSpecificPosition(); - - if (position == -1 && holder->getType() == building_type::Weaponrack) - { - for (size_t i = 0; i < squad->positions.size(); i++) - { - if (binsearch_index(squad->positions[i]->assigned_items, item->id) >= 0) - return true; - } - } - else - { - auto cpos = vector_get(squad->positions, position); - if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0) - return true; - } - - return false; -} - -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(); - if (!holder) - return false; - - return belongs_to_position(item, holder); -} - -template struct armory_hook : Item { - typedef Item interpose_base; - - DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) - { - 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); -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); -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") @@ -1029,26 +835,6 @@ static command_result tweak(color_ostream &out, vector ¶meters) { enable_hook(out, INTERPOSE_HOOK(military_assign_hook, render), parameters); } - else if (cmd == "armory") - { - 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); - 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); - 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;