From 75c560d9e146e6eb5354a54909c4f1175c197521 Mon Sep 17 00:00:00 2001 From: Rose Date: Sun, 29 Jan 2023 17:19:21 -0800 Subject: [PATCH 1/8] Add in a strictness parameter to Autoclothing. It's not used yet. --- plugins/autoclothing.cpp | 117 ++++++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 44 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 4b795c02f..f2e3578ee 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -1,7 +1,8 @@ #include "Core.h" -#include -#include -#include +#include "Console.h" +#include "Debug.h" +#include "Export.h" +#include "PluginManager.h" #include @@ -11,6 +12,7 @@ #include "modules/Items.h" #include "modules/Maps.h" #include "modules/Materials.h" +#include "modules/Persistence.h" #include "modules/Units.h" #include "modules/World.h" #include "modules/Translation.h" @@ -34,6 +36,9 @@ #include "df/world.h" using std::endl; +using std::string; +using std::vector; +using std::map; using namespace DFHack; using namespace DFHack::Items; @@ -41,35 +46,46 @@ using namespace DFHack::Units; using namespace df::enums; -// A plugin must be able to return its name and version. -// The name string provided must correspond to the filename - -// skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case DFHACK_PLUGIN("autoclothing"); - -// Any globals a plugin requires (e.g. world) should be listed here. -// For example, this line expands to "using df::global::world" and prevents the -// plugin from being loaded if df::global::world is null (i.e. missing from symbols.xml): -// REQUIRE_GLOBAL(world); // Only run if this is enabled DFHACK_PLUGIN_IS_ENABLED(autoclothing_enabled); +// logging levels can be dynamically controlled with the `debugfilter` command. +namespace DFHack { + // for configuration-related logging + DBG_DECLARE(autoclothing, status, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(autoclothing, cycle, DebugCategory::LINFO); +} + +static const string CONFIG_KEY = string(plugin_name) + "/config"; + + // Here go all the command declarations... // mostly to allow having the mandatory stuff on top of the file and commands on the bottom struct ClothingRequirement; -command_result autoclothing(color_ostream &out, std::vector & parameters); +command_result autoclothing(color_ostream &out, vector & parameters); static void init_state(color_ostream &out); static void save_state(color_ostream &out); static void cleanup_state(color_ostream &out); static void do_autoclothing(); static bool validateMaterialCategory(ClothingRequirement * requirement); -static bool setItem(std::string name, ClothingRequirement* requirement); +static bool setItem(string name, ClothingRequirement* requirement); static void generate_report(color_ostream& out); static bool isAvailableItem(df::item* item); +enum match_strictness +{ + STRICT_PERMISSIVE, + STRICT_TYPE, + STRICT_MATERIAL +}; + +match_strictness strictnessSetting = STRICT_PERMISSIVE; -std::vectorclothingOrders; +vectorclothingOrders; struct ClothingRequirement { @@ -78,7 +94,7 @@ struct ClothingRequirement int16_t item_subtype; df::job_material_category material_category; int16_t needed_per_citizen; - std::map total_needed_per_race; + map total_needed_per_race; bool matches(ClothingRequirement * b) { @@ -93,7 +109,7 @@ struct ClothingRequirement return true; } - std::string Serialize() + string Serialize() { std::stringstream stream; stream << ENUM_KEY_STR(job_type, jobType) << " "; @@ -104,10 +120,10 @@ struct ClothingRequirement return stream.str(); } - void Deserialize(std::string s) + void Deserialize(string s) { std::stringstream stream(s); - std::string loadedJob; + string loadedJob; stream >> loadedJob; FOR_ENUM_ITEMS(job_type, job) { @@ -117,7 +133,7 @@ struct ClothingRequirement break; } } - std::string loadedItem; + string loadedItem; stream >> loadedItem; FOR_ENUM_ITEMS(item_type, item) { @@ -132,7 +148,7 @@ struct ClothingRequirement stream >> needed_per_citizen; } - bool SetFromParameters(color_ostream &out, std::vector & parameters) + bool SetFromParameters(color_ostream &out, vector & parameters) { if (!set_bitfield_field(&material_category, parameters[0], 1)) { @@ -151,12 +167,12 @@ struct ClothingRequirement return true; } - std::string ToReadableLabel() + string ToReadableLabel() { std::stringstream stream; stream << bitfield_to_string(material_category) << " "; - std::string adjective = ""; - std::string name = ""; + string adjective = ""; + string name = ""; switch (itemType) { case df::enums::item_type::ARMOR: @@ -193,7 +209,7 @@ struct ClothingRequirement // Mandatory init function. If you have some global state, create it here. -DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) +DFhackCExport command_result plugin_init(color_ostream &out, vector &commands) { // Fill the command list with your commands. commands.push_back(PluginCommand( @@ -256,12 +272,12 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) return CR_OK; } -static bool setItemFromName(std::string name, ClothingRequirement* requirement) +static bool setItemFromName(string name, ClothingRequirement* requirement) { #define SEARCH_ITEM_RAWS(rawType, job, item) \ for (auto& itemdef : world->raws.itemdefs.rawType) \ { \ - std::string fullName = itemdef->adjective.empty() ? itemdef->name : itemdef->adjective + " " + itemdef->name; \ + string fullName = itemdef->adjective.empty() ? itemdef->name : itemdef->adjective + " " + itemdef->name; \ if (fullName == name) \ { \ requirement->jobType = job_type::job; \ @@ -278,7 +294,7 @@ for (auto& itemdef : world->raws.itemdefs.rawType) \ return false; } -static bool setItemFromToken(std::string token, ClothingRequirement* requirement) +static bool setItemFromToken(string token, ClothingRequirement* requirement) { ItemTypeInfo itemInfo; if (!itemInfo.find(token)) @@ -308,7 +324,7 @@ static bool setItemFromToken(std::string token, ClothingRequirement* requirement return true; } -static bool setItem(std::string name, ClothingRequirement* requirement) +static bool setItem(string name, ClothingRequirement* requirement) { if (setItemFromName(name, requirement)) return true; @@ -362,7 +378,7 @@ static bool validateMaterialCategory(ClothingRequirement * requirement) // A command! It sits around and looks pretty. And it's nice and friendly. -command_result autoclothing(color_ostream &out, std::vector & parameters) +command_result autoclothing(color_ostream &out, vector & parameters) { // It's nice to print a help message you get invalid options // from the user instead of just acting strange. @@ -379,6 +395,20 @@ command_result autoclothing(color_ostream &out, std::vector & para } return CR_OK; } + else if (parameters[0] == "strictness") + { + if (parameters.size() != 2) + { + out << "Wrong number of arguments." << endl; + return CR_WRONG_USAGE; + } + if (parameters[1] == "permissive") + strictnessSetting = STRICT_PERMISSIVE; + else if (parameters[1] == "type") + strictnessSetting = STRICT_TYPE; + else if (parameters[1] == "material") + strictnessSetting = STRICT_MATERIAL; + } else if (parameters.size() == 1 && parameters[0] == "report") { CoreSuspender suspend; @@ -631,9 +661,8 @@ static void init_state(color_ostream &out) autoclothing_enabled = false; } - // Parse constraints - std::vector items; + vector items; World::GetPersistentData(&items, "autoclothing/clothingItems"); for (auto& item : items) @@ -662,7 +691,7 @@ static void save_state(color_ostream &out) // Parse constraints - std::vector items; + vector items; World::GetPersistentData(&items, "autoclothing/clothingItems"); for (size_t i = 0; i < items.size(); i++) @@ -683,7 +712,7 @@ static void save_state(color_ostream &out) } } -static void list_unit_counts(color_ostream& out, std::map& unitList) +static void list_unit_counts(color_ostream& out, map& unitList) { for (const auto& race : unitList) { @@ -733,12 +762,12 @@ static bool isAvailableItem(df::item* item) static void generate_report(color_ostream& out) { - std::map fullUnitList; - std::map missingArmor; - std::map missingShoes; - std::map missingHelms; - std::map missingGloves; - std::map missingPants; + map fullUnitList; + map missingArmor; + map missingShoes; + map missingHelms; + map missingGloves; + map missingPants; for (df::unit* unit : world->units.active) { if (!Units::isCitizen(unit)) @@ -817,7 +846,7 @@ static void generate_report(color_ostream& out) list_unit_counts(out, missingPants); } } - std::map availableArmor; + map availableArmor; for (auto armor : world->items.other.ARMOR) { if (!isAvailableItem(armor)) @@ -829,7 +858,7 @@ static void generate_report(color_ostream& out) out << "We have available bodywear for:" << endl; list_unit_counts(out, availableArmor); } - std::map availableShoes; + map availableShoes; for (auto shoe : world->items.other.SHOES) { if (!isAvailableItem(shoe)) @@ -841,7 +870,7 @@ static void generate_report(color_ostream& out) out << "We have available footwear for:" << endl; list_unit_counts(out, availableShoes); } - std::map availableHelms; + map availableHelms; for (auto helm : world->items.other.HELM) { if (!isAvailableItem(helm)) @@ -853,7 +882,7 @@ static void generate_report(color_ostream& out) out << "We have available headwear for:" << endl; list_unit_counts(out, availableHelms); } - std::map availableGloves; + map availableGloves; for (auto glove : world->items.other.HELM) { if (!isAvailableItem(glove)) @@ -865,7 +894,7 @@ static void generate_report(color_ostream& out) out << "We have available handwear for:" << endl; list_unit_counts(out, availableGloves); } - std::map availablePants; + map availablePants; for (auto pants : world->items.other.HELM) { if (!isAvailableItem(pants)) From c79b95ec3362673ede6198dd85994ec1b43da6aa Mon Sep 17 00:00:00 2001 From: Rose Date: Sun, 29 Jan 2023 17:48:23 -0800 Subject: [PATCH 2/8] Don't crash autoclothing if a dwarf has a broken inventory item. --- plugins/autoclothing.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index f2e3578ee..bdf525a4c 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -55,7 +55,7 @@ DFHACK_PLUGIN_IS_ENABLED(autoclothing_enabled); // logging levels can be dynamically controlled with the `debugfilter` command. namespace DFHack { // for configuration-related logging - DBG_DECLARE(autoclothing, status, DebugCategory::LINFO); + DBG_DECLARE(autoclothing, report, DebugCategory::LINFO); // for logging during the periodic scan DBG_DECLARE(autoclothing, cycle, DebugCategory::LINFO); } @@ -524,6 +524,12 @@ static void find_needed_clothing_items() { auto item = findItemByID(ownedItem); + if (!item) + { + WARN(cycle).print("Invalid inventory item ID: %d\n", ownedItem); + continue; + } + if (item->getType() != clothingOrder.itemType) continue; if (item->getSubtype() != clothingOrder.item_subtype) @@ -811,7 +817,7 @@ static void generate_report(color_ostream& out) missingGloves[unit->race]++; if (numPants == 0) missingPants[unit->race]++; - //out << Translation::TranslateName(Units::getVisibleName(unit)) << " has " << numArmor << " armor, " << numShoes << " shoes, " << numHelms << " helms, " << numGloves << " gloves, " << numPants << " pants" << endl; + DEBUG(report) << Translation::TranslateName(Units::getVisibleName(unit)) << " has " << numArmor << " armor, " << numShoes << " shoes, " << numHelms << " helms, " << numGloves << " gloves, " << numPants << " pants" << endl; } if (missingArmor.size() + missingShoes.size() + missingHelms.size() + missingGloves.size() + missingPants.size() == 0) { From c9ddb4d9436de86f6f43fd27d625b742368d36ae Mon Sep 17 00:00:00 2001 From: Rose Date: Sun, 29 Jan 2023 17:51:10 -0800 Subject: [PATCH 3/8] There was one more place for autoclothing to crash. --- plugins/autoclothing.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index bdf525a4c..d7dee23ad 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -520,7 +520,7 @@ static void find_needed_clothing_items() int alreadyOwnedAmount = 0; //looping through the items first, then clothing order might be a little faster, but this way is cleaner. - for (auto& ownedItem : unit->owned_items) + for (auto ownedItem : unit->owned_items) { auto item = findItemByID(ownedItem); @@ -782,8 +782,12 @@ static void generate_report(color_ostream& out) int numArmor = 0, numShoes = 0, numHelms = 0, numGloves = 0, numPants = 0; for (auto itemId : unit->owned_items) { - auto item = Items::findItemByID(itemId); + if (!item) + { + WARN(cycle).print("Invalid inventory item ID: %d\n", itemId); + continue; + } if (item->getWear() >= 1) continue; switch (item->getType()) From ea22a23ab7b8763542750194cb8934675cea3336 Mon Sep 17 00:00:00 2001 From: Rose Date: Thu, 2 Feb 2023 13:06:14 -0800 Subject: [PATCH 4/8] Clean up autoclothing for release. --- docs/changelog.txt | 2 ++ plugins/autoclothing.cpp | 35 ++++++++++++++++------------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 1174508e2..4e8af42ed 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -40,6 +40,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: -@ ``Screen``: allow `gui/launcher` and `gui/quickcmd` to launch themselves without hanging the game -@ Fix issues with clicks "passing through" some DFHack window elements, like scrollbars - `getplants`: tree are now designated correctly +- `autoclothing`: fixed a crash that can happen when units are holding invalid items. ## Misc Improvements - A new cross-compile build script was added for building the Windows files from a Linux Docker builder (see the Compile instructions in the docs) @@ -52,6 +53,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `overlay`: overlay widgets can now specify a default enabled state if they are not already set in the player's overlay config file - `getplants`: ID values will now be accepted regardless of case -@ New borders for DFHack tool windows -- tell us what you think! +- `autoclothing`: merged the two separate reports into the same command. ## Documentation diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index d7dee23ad..4aa835b8b 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -388,33 +388,30 @@ command_result autoclothing(color_ostream &out, vector & parameters) // be used by 'help your-command'. if (parameters.size() == 0) { + CoreSuspender suspend; out << "Currently set " << clothingOrders.size() << " automatic clothing orders" << endl; for (size_t i = 0; i < clothingOrders.size(); i++) { out << clothingOrders[i].ToReadableLabel() << endl; } - return CR_OK; - } - else if (parameters[0] == "strictness") - { - if (parameters.size() != 2) - { - out << "Wrong number of arguments." << endl; - return CR_WRONG_USAGE; - } - if (parameters[1] == "permissive") - strictnessSetting = STRICT_PERMISSIVE; - else if (parameters[1] == "type") - strictnessSetting = STRICT_TYPE; - else if (parameters[1] == "material") - strictnessSetting = STRICT_MATERIAL; - } - else if (parameters.size() == 1 && parameters[0] == "report") - { - CoreSuspender suspend; generate_report(out); return CR_OK; } + ////Disabled until I have time to fully implement it. + //else if (parameters[0] == "strictness") + //{ + // if (parameters.size() != 2) + // { + // out << "Wrong number of arguments." << endl; + // return CR_WRONG_USAGE; + // } + // if (parameters[1] == "permissive") + // strictnessSetting = STRICT_PERMISSIVE; + // else if (parameters[1] == "type") + // strictnessSetting = STRICT_TYPE; + // else if (parameters[1] == "material") + // strictnessSetting = STRICT_MATERIAL; + //} else if (parameters.size() < 2 || parameters.size() > 3) { out << "Wrong number of arguments." << endl; From 7d304dbaf87b761a9c9fcac1909d81d3dfe2fe61 Mon Sep 17 00:00:00 2001 From: Rose Date: Thu, 2 Feb 2023 13:08:23 -0800 Subject: [PATCH 5/8] Update autoclothing docs. --- docs/plugins/autoclothing.rst | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/plugins/autoclothing.rst b/docs/plugins/autoclothing.rst index 9df013d19..6e8ae7df2 100644 --- a/docs/plugins/autoclothing.rst +++ b/docs/plugins/autoclothing.rst @@ -15,18 +15,15 @@ Usage autoclothing autoclothing [quantity] - autoclothing report ``material`` can be "cloth", "silk", "yarn", or "leather". The ``item`` can be anything your civilization can produce, such as "dress" or "mitten". When invoked without parameters, it shows a summary of all managed clothing -orders. When invoked with a material and item, but without a quantity, it shows +orders, and the overall clothing situation in your fort. +When invoked with a material and item, but without a quantity, it shows the current configuration for that material and item. -``report`` gives a list of how many units of each race in your fortress that are -missing clothing in each available slot, as well as how much spare clothing you -have per slot, per race. Examples -------- @@ -35,5 +32,3 @@ Examples Sets the desired number of cloth short skirts available per citizen to 10. ``autoclothing cloth dress`` Displays the currently set number of cloth dresses chosen per citizen. -``autoclothing report`` - Displays a report of your clothing situation. From 9c8903dfbf14a003e551969ebb74bf83baf2df03 Mon Sep 17 00:00:00 2001 From: Rose Date: Thu, 2 Feb 2023 13:32:44 -0800 Subject: [PATCH 6/8] Update docs/plugins/autoclothing.rst Co-authored-by: Myk --- docs/plugins/autoclothing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/autoclothing.rst b/docs/plugins/autoclothing.rst index 6e8ae7df2..4584791bd 100644 --- a/docs/plugins/autoclothing.rst +++ b/docs/plugins/autoclothing.rst @@ -20,7 +20,7 @@ Usage anything your civilization can produce, such as "dress" or "mitten". When invoked without parameters, it shows a summary of all managed clothing -orders, and the overall clothing situation in your fort. +orders, and the overall clothing situation in your fort. When invoked with a material and item, but without a quantity, it shows the current configuration for that material and item. From b36e5e1dffbc5cb7546482258ebce030c4d9412f Mon Sep 17 00:00:00 2001 From: Rose Date: Thu, 2 Feb 2023 13:33:20 -0800 Subject: [PATCH 7/8] Update plugins/autoclothing.cpp Co-authored-by: Myk --- plugins/autoclothing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 4aa835b8b..258668499 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -782,7 +782,7 @@ static void generate_report(color_ostream& out) auto item = Items::findItemByID(itemId); if (!item) { - WARN(cycle).print("Invalid inventory item ID: %d\n", itemId); + WARN(cycle,out).print("Invalid inventory item ID: %d\n", itemId); continue; } if (item->getWear() >= 1) From 6e200b831eb55149359690fb6ffb66e2379335c6 Mon Sep 17 00:00:00 2001 From: Rose Date: Thu, 2 Feb 2023 13:33:30 -0800 Subject: [PATCH 8/8] Update plugins/autoclothing.cpp Co-authored-by: Myk --- plugins/autoclothing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index 258668499..fc8b1e43b 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -818,7 +818,7 @@ static void generate_report(color_ostream& out) missingGloves[unit->race]++; if (numPants == 0) missingPants[unit->race]++; - DEBUG(report) << Translation::TranslateName(Units::getVisibleName(unit)) << " has " << numArmor << " armor, " << numShoes << " shoes, " << numHelms << " helms, " << numGloves << " gloves, " << numPants << " pants" << endl; + DEBUG(report,out) << Translation::TranslateName(Units::getVisibleName(unit)) << " has " << numArmor << " armor, " << numShoes << " shoes, " << numHelms << " helms, " << numGloves << " gloves, " << numPants << " pants" << endl; } if (missingArmor.size() + missingShoes.size() + missingHelms.size() + missingGloves.size() + missingPants.size() == 0) {