Merge pull request #2784 from RosaryMala/Autoclothing

Autoclothing
develop
Myk 2023-02-02 13:37:07 -08:00 committed by GitHub
commit e1c07817ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 59 deletions

@ -41,6 +41,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
-@ allow launcher tools to launch themselves without hanging the game -@ allow launcher tools to launch themselves without hanging the game
-@ fix issues with clicks "passing through" some DFHack window elements to the screen below -@ fix issues with clicks "passing through" some DFHack window elements to the screen below
- `getplants`: trees are now designated correctly - `getplants`: trees are now designated correctly
- `autoclothing`: fixed a crash that can happen when units are holding invalid items.
-@ `orders`: fix orders in library/basic that create bags -@ `orders`: fix orders in library/basic that create bags
- `orders`: library/military now sticks to vanilla rules and does not add orders for normally-mood-only platinum weapons. A new library orders file ``library/military_include_artifact_materials`` is now offered as an alternate ``library/military`` set of orders that still includes the platinum weapons. - `orders`: library/military now sticks to vanilla rules and does not add orders for normally-mood-only platinum weapons. A new library orders file ``library/military_include_artifact_materials`` is now offered as an alternate ``library/military`` set of orders that still includes the platinum weapons.
@ -49,6 +50,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- `getplants`: ID values will now be accepted regardless of case - `getplants`: ID values will now be accepted regardless of case
- Windows now display "PAUSE FORCED" on the lower border if the tool is forcing the game to pause - Windows now display "PAUSE FORCED" on the lower border if the tool is forcing the game to pause
-@ New borders for DFHack tool windows -- tell us what you think! -@ New borders for DFHack tool windows -- tell us what you think!
- `autoclothing`: merged the two separate reports into the same command.
- `automelt`: stockpile configuration can now be set from the commandline - `automelt`: stockpile configuration can now be set from the commandline
- `channel-safely`: new monitoring for cave-in prevention - `channel-safely`: new monitoring for cave-in prevention
- `gui/control-panel`: you can now configure whether DFHack tool windows should pause the game by default - `gui/control-panel`: you can now configure whether DFHack tool windows should pause the game by default

@ -15,18 +15,15 @@ Usage
autoclothing autoclothing
autoclothing <material> <item> [quantity] autoclothing <material> <item> [quantity]
autoclothing report
``material`` can be "cloth", "silk", "yarn", or "leather". The ``item`` can be ``material`` can be "cloth", "silk", "yarn", or "leather". The ``item`` can be
anything your civilization can produce, such as "dress" or "mitten". anything your civilization can produce, such as "dress" or "mitten".
When invoked without parameters, it shows a summary of all managed clothing 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. 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 Examples
-------- --------
@ -35,5 +32,3 @@ Examples
Sets the desired number of cloth short skirts available per citizen to 10. Sets the desired number of cloth short skirts available per citizen to 10.
``autoclothing cloth dress`` ``autoclothing cloth dress``
Displays the currently set number of cloth dresses chosen per citizen. Displays the currently set number of cloth dresses chosen per citizen.
``autoclothing report``
Displays a report of your clothing situation.

@ -1,7 +1,8 @@
#include "Core.h" #include "Core.h"
#include <Console.h> #include "Console.h"
#include <Export.h> #include "Debug.h"
#include <PluginManager.h> #include "Export.h"
#include "PluginManager.h"
#include <map> #include <map>
@ -11,6 +12,7 @@
#include "modules/Items.h" #include "modules/Items.h"
#include "modules/Maps.h" #include "modules/Maps.h"
#include "modules/Materials.h" #include "modules/Materials.h"
#include "modules/Persistence.h"
#include "modules/Units.h" #include "modules/Units.h"
#include "modules/World.h" #include "modules/World.h"
#include "modules/Translation.h" #include "modules/Translation.h"
@ -34,6 +36,9 @@
#include "df/world.h" #include "df/world.h"
using std::endl; using std::endl;
using std::string;
using std::vector;
using std::map;
using namespace DFHack; using namespace DFHack;
using namespace DFHack::Items; using namespace DFHack::Items;
@ -41,35 +46,46 @@ using namespace DFHack::Units;
using namespace df::enums; 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"); 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); REQUIRE_GLOBAL(world);
// Only run if this is enabled // Only run if this is enabled
DFHACK_PLUGIN_IS_ENABLED(autoclothing_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, report, 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... // Here go all the command declarations...
// mostly to allow having the mandatory stuff on top of the file and commands on the bottom // mostly to allow having the mandatory stuff on top of the file and commands on the bottom
struct ClothingRequirement; struct ClothingRequirement;
command_result autoclothing(color_ostream &out, std::vector <std::string> & parameters); command_result autoclothing(color_ostream &out, vector <string> & parameters);
static void init_state(color_ostream &out); static void init_state(color_ostream &out);
static void save_state(color_ostream &out); static void save_state(color_ostream &out);
static void cleanup_state(color_ostream &out); static void cleanup_state(color_ostream &out);
static void do_autoclothing(); static void do_autoclothing();
static bool validateMaterialCategory(ClothingRequirement * requirement); 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 void generate_report(color_ostream& out);
static bool isAvailableItem(df::item* item); static bool isAvailableItem(df::item* item);
enum match_strictness
{
STRICT_PERMISSIVE,
STRICT_TYPE,
STRICT_MATERIAL
};
match_strictness strictnessSetting = STRICT_PERMISSIVE;
std::vector<ClothingRequirement>clothingOrders; vector<ClothingRequirement>clothingOrders;
struct ClothingRequirement struct ClothingRequirement
{ {
@ -78,7 +94,7 @@ struct ClothingRequirement
int16_t item_subtype; int16_t item_subtype;
df::job_material_category material_category; df::job_material_category material_category;
int16_t needed_per_citizen; int16_t needed_per_citizen;
std::map<int16_t, int32_t> total_needed_per_race; map<int16_t, int32_t> total_needed_per_race;
bool matches(ClothingRequirement * b) bool matches(ClothingRequirement * b)
{ {
@ -93,7 +109,7 @@ struct ClothingRequirement
return true; return true;
} }
std::string Serialize() string Serialize()
{ {
std::stringstream stream; std::stringstream stream;
stream << ENUM_KEY_STR(job_type, jobType) << " "; stream << ENUM_KEY_STR(job_type, jobType) << " ";
@ -104,10 +120,10 @@ struct ClothingRequirement
return stream.str(); return stream.str();
} }
void Deserialize(std::string s) void Deserialize(string s)
{ {
std::stringstream stream(s); std::stringstream stream(s);
std::string loadedJob; string loadedJob;
stream >> loadedJob; stream >> loadedJob;
FOR_ENUM_ITEMS(job_type, job) FOR_ENUM_ITEMS(job_type, job)
{ {
@ -117,7 +133,7 @@ struct ClothingRequirement
break; break;
} }
} }
std::string loadedItem; string loadedItem;
stream >> loadedItem; stream >> loadedItem;
FOR_ENUM_ITEMS(item_type, item) FOR_ENUM_ITEMS(item_type, item)
{ {
@ -132,7 +148,7 @@ struct ClothingRequirement
stream >> needed_per_citizen; stream >> needed_per_citizen;
} }
bool SetFromParameters(color_ostream &out, std::vector <std::string> & parameters) bool SetFromParameters(color_ostream &out, vector <string> & parameters)
{ {
if (!set_bitfield_field(&material_category, parameters[0], 1)) if (!set_bitfield_field(&material_category, parameters[0], 1))
{ {
@ -151,12 +167,12 @@ struct ClothingRequirement
return true; return true;
} }
std::string ToReadableLabel() string ToReadableLabel()
{ {
std::stringstream stream; std::stringstream stream;
stream << bitfield_to_string(material_category) << " "; stream << bitfield_to_string(material_category) << " ";
std::string adjective = ""; string adjective = "";
std::string name = ""; string name = "";
switch (itemType) switch (itemType)
{ {
case df::enums::item_type::ARMOR: case df::enums::item_type::ARMOR:
@ -193,7 +209,7 @@ struct ClothingRequirement
// Mandatory init function. If you have some global state, create it here. // Mandatory init function. If you have some global state, create it here.
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init(color_ostream &out, vector <PluginCommand> &commands)
{ {
// Fill the command list with your commands. // Fill the command list with your commands.
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
@ -256,12 +272,12 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
return CR_OK; 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) \ #define SEARCH_ITEM_RAWS(rawType, job, item) \
for (auto& itemdef : world->raws.itemdefs.rawType) \ 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) \ if (fullName == name) \
{ \ { \
requirement->jobType = job_type::job; \ requirement->jobType = job_type::job; \
@ -278,7 +294,7 @@ for (auto& itemdef : world->raws.itemdefs.rawType) \
return false; return false;
} }
static bool setItemFromToken(std::string token, ClothingRequirement* requirement) static bool setItemFromToken(string token, ClothingRequirement* requirement)
{ {
ItemTypeInfo itemInfo; ItemTypeInfo itemInfo;
if (!itemInfo.find(token)) if (!itemInfo.find(token))
@ -308,7 +324,7 @@ static bool setItemFromToken(std::string token, ClothingRequirement* requirement
return true; return true;
} }
static bool setItem(std::string name, ClothingRequirement* requirement) static bool setItem(string name, ClothingRequirement* requirement)
{ {
if (setItemFromName(name, requirement)) if (setItemFromName(name, requirement))
return true; 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. // A command! It sits around and looks pretty. And it's nice and friendly.
command_result autoclothing(color_ostream &out, std::vector <std::string> & parameters) command_result autoclothing(color_ostream &out, vector <string> & parameters)
{ {
// It's nice to print a help message you get invalid options // It's nice to print a help message you get invalid options
// from the user instead of just acting strange. // from the user instead of just acting strange.
@ -372,19 +388,30 @@ command_result autoclothing(color_ostream &out, std::vector <std::string> & para
// be used by 'help your-command'. // be used by 'help your-command'.
if (parameters.size() == 0) if (parameters.size() == 0)
{ {
CoreSuspender suspend;
out << "Currently set " << clothingOrders.size() << " automatic clothing orders" << endl; out << "Currently set " << clothingOrders.size() << " automatic clothing orders" << endl;
for (size_t i = 0; i < clothingOrders.size(); i++) for (size_t i = 0; i < clothingOrders.size(); i++)
{ {
out << clothingOrders[i].ToReadableLabel() << endl; out << clothingOrders[i].ToReadableLabel() << endl;
} }
return CR_OK;
}
else if (parameters.size() == 1 && parameters[0] == "report")
{
CoreSuspender suspend;
generate_report(out); generate_report(out);
return CR_OK; 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) else if (parameters.size() < 2 || parameters.size() > 3)
{ {
out << "Wrong number of arguments." << endl; out << "Wrong number of arguments." << endl;
@ -490,10 +517,16 @@ static void find_needed_clothing_items()
int alreadyOwnedAmount = 0; int alreadyOwnedAmount = 0;
//looping through the items first, then clothing order might be a little faster, but this way is cleaner. //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); auto item = findItemByID(ownedItem);
if (!item)
{
WARN(cycle).print("Invalid inventory item ID: %d\n", ownedItem);
continue;
}
if (item->getType() != clothingOrder.itemType) if (item->getType() != clothingOrder.itemType)
continue; continue;
if (item->getSubtype() != clothingOrder.item_subtype) if (item->getSubtype() != clothingOrder.item_subtype)
@ -631,9 +664,8 @@ static void init_state(color_ostream &out)
autoclothing_enabled = false; autoclothing_enabled = false;
} }
// Parse constraints // Parse constraints
std::vector<PersistentDataItem> items; vector<PersistentDataItem> items;
World::GetPersistentData(&items, "autoclothing/clothingItems"); World::GetPersistentData(&items, "autoclothing/clothingItems");
for (auto& item : items) for (auto& item : items)
@ -662,7 +694,7 @@ static void save_state(color_ostream &out)
// Parse constraints // Parse constraints
std::vector<PersistentDataItem> items; vector<PersistentDataItem> items;
World::GetPersistentData(&items, "autoclothing/clothingItems"); World::GetPersistentData(&items, "autoclothing/clothingItems");
for (size_t i = 0; i < items.size(); i++) for (size_t i = 0; i < items.size(); i++)
@ -683,7 +715,7 @@ static void save_state(color_ostream &out)
} }
} }
static void list_unit_counts(color_ostream& out, std::map<int, int>& unitList) static void list_unit_counts(color_ostream& out, map<int, int>& unitList)
{ {
for (const auto& race : unitList) for (const auto& race : unitList)
{ {
@ -733,12 +765,12 @@ static bool isAvailableItem(df::item* item)
static void generate_report(color_ostream& out) static void generate_report(color_ostream& out)
{ {
std::map<int, int> fullUnitList; map<int, int> fullUnitList;
std::map<int, int> missingArmor; map<int, int> missingArmor;
std::map<int, int> missingShoes; map<int, int> missingShoes;
std::map<int, int> missingHelms; map<int, int> missingHelms;
std::map<int, int> missingGloves; map<int, int> missingGloves;
std::map<int, int> missingPants; map<int, int> missingPants;
for (df::unit* unit : world->units.active) for (df::unit* unit : world->units.active)
{ {
if (!Units::isCitizen(unit)) if (!Units::isCitizen(unit))
@ -747,8 +779,12 @@ static void generate_report(color_ostream& out)
int numArmor = 0, numShoes = 0, numHelms = 0, numGloves = 0, numPants = 0; int numArmor = 0, numShoes = 0, numHelms = 0, numGloves = 0, numPants = 0;
for (auto itemId : unit->owned_items) for (auto itemId : unit->owned_items)
{ {
auto item = Items::findItemByID(itemId); auto item = Items::findItemByID(itemId);
if (!item)
{
WARN(cycle,out).print("Invalid inventory item ID: %d\n", itemId);
continue;
}
if (item->getWear() >= 1) if (item->getWear() >= 1)
continue; continue;
switch (item->getType()) switch (item->getType())
@ -782,7 +818,7 @@ static void generate_report(color_ostream& out)
missingGloves[unit->race]++; missingGloves[unit->race]++;
if (numPants == 0) if (numPants == 0)
missingPants[unit->race]++; missingPants[unit->race]++;
//out << 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) if (missingArmor.size() + missingShoes.size() + missingHelms.size() + missingGloves.size() + missingPants.size() == 0)
{ {
@ -817,7 +853,7 @@ static void generate_report(color_ostream& out)
list_unit_counts(out, missingPants); list_unit_counts(out, missingPants);
} }
} }
std::map<int, int> availableArmor; map<int, int> availableArmor;
for (auto armor : world->items.other.ARMOR) for (auto armor : world->items.other.ARMOR)
{ {
if (!isAvailableItem(armor)) if (!isAvailableItem(armor))
@ -829,7 +865,7 @@ static void generate_report(color_ostream& out)
out << "We have available bodywear for:" << endl; out << "We have available bodywear for:" << endl;
list_unit_counts(out, availableArmor); list_unit_counts(out, availableArmor);
} }
std::map<int, int> availableShoes; map<int, int> availableShoes;
for (auto shoe : world->items.other.SHOES) for (auto shoe : world->items.other.SHOES)
{ {
if (!isAvailableItem(shoe)) if (!isAvailableItem(shoe))
@ -841,7 +877,7 @@ static void generate_report(color_ostream& out)
out << "We have available footwear for:" << endl; out << "We have available footwear for:" << endl;
list_unit_counts(out, availableShoes); list_unit_counts(out, availableShoes);
} }
std::map<int, int> availableHelms; map<int, int> availableHelms;
for (auto helm : world->items.other.HELM) for (auto helm : world->items.other.HELM)
{ {
if (!isAvailableItem(helm)) if (!isAvailableItem(helm))
@ -853,7 +889,7 @@ static void generate_report(color_ostream& out)
out << "We have available headwear for:" << endl; out << "We have available headwear for:" << endl;
list_unit_counts(out, availableHelms); list_unit_counts(out, availableHelms);
} }
std::map<int, int> availableGloves; map<int, int> availableGloves;
for (auto glove : world->items.other.HELM) for (auto glove : world->items.other.HELM)
{ {
if (!isAvailableItem(glove)) if (!isAvailableItem(glove))
@ -865,7 +901,7 @@ static void generate_report(color_ostream& out)
out << "We have available handwear for:" << endl; out << "We have available handwear for:" << endl;
list_unit_counts(out, availableGloves); list_unit_counts(out, availableGloves);
} }
std::map<int, int> availablePants; map<int, int> availablePants;
for (auto pants : world->items.other.HELM) for (auto pants : world->items.other.HELM)
{ {
if (!isAvailableItem(pants)) if (!isAvailableItem(pants))