Merge pull request #2643 from RosaryMala/Autoclothing

Autoclothing
develop
Myk 2023-01-20 14:08:01 -08:00 committed by GitHub
commit 15ba54eea9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 268 additions and 14 deletions

@ -15,6 +15,7 @@ Usage
autoclothing
autoclothing <material> <item> [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".
@ -23,6 +24,10 @@ 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
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
--------
@ -30,3 +35,5 @@ 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.

@ -179,6 +179,8 @@ DFHACK_EXPORT std::string getRaceName(df::unit* unit);
DFHACK_EXPORT std::string getPhysicalDescription(df::unit* unit);
DFHACK_EXPORT std::string getRaceNamePluralById(int32_t race_id);
DFHACK_EXPORT std::string getRaceNamePlural(df::unit* unit);
DFHACK_EXPORT std::string getRaceReadableNameById(int32_t race_id);
DFHACK_EXPORT std::string getRaceReadableName(df::unit* unit);
DFHACK_EXPORT std::string getRaceBabyNameById(int32_t race_id);
DFHACK_EXPORT std::string getRaceBabyName(df::unit* unit);
DFHACK_EXPORT std::string getRaceChildNameById(int32_t race_id);

@ -1024,10 +1024,13 @@ df::unit_misc_trait *Units::getMiscTrait(df::unit *unit, df::misc_trait_type typ
// get race name by id or unit pointer
string Units::getRaceNameById(int32_t id)
{
if (id >= 0 && (size_t)id < world->raws.creatures.all.size())
{
df::creature_raw* raw = world->raws.creatures.all[id];
if (raw)
return raw->creature_id;
}
return "";
}
string Units::getRaceName(df::unit* unit)
@ -1036,6 +1039,23 @@ string Units::getRaceName(df::unit* unit)
return getRaceNameById(unit->race);
}
// get human-readable race name by id or unit pointer
string Units::getRaceReadableNameById(int32_t id)
{
if (id >= 0 && (size_t)id < world->raws.creatures.all.size())
{
df::creature_raw* raw = world->raws.creatures.all[id];
if (raw)
return raw->name[0];
}
return "";
}
string Units::getRaceReadableName(df::unit* unit)
{
CHECK_NULL_POINTER(unit);
return getRaceReadableNameById(unit->race);
}
void df_unit_get_physical_description(df::unit* unit, string* out_str)
{
static auto* const fn =
@ -1057,10 +1077,13 @@ string Units::getPhysicalDescription(df::unit* unit)
// get plural of race name (used for display in autobutcher UI and for sorting the watchlist)
string Units::getRaceNamePluralById(int32_t id)
{
if (id >= 0 && (size_t)id < world->raws.creatures.all.size())
{
df::creature_raw* raw = world->raws.creatures.all[id];
if (raw)
return raw->name[1]; // second field is plural of race name
}
return "";
}
@ -1071,10 +1094,13 @@ string Units::getRaceNamePlural(df::unit* unit)
}
string Units::getRaceBabyNameById(int32_t id)
{
if (id >= 0 && (size_t)id < world->raws.creatures.all.size())
{
df::creature_raw* raw = world->raws.creatures.all[id];
if (raw)
return raw->general_baby_name[0];
}
return "";
}
@ -1085,10 +1111,13 @@ string Units::getRaceBabyName(df::unit* unit)
}
string Units::getRaceChildNameById(int32_t id)
{
if (id >= 0 && (size_t)id < world->raws.creatures.all.size())
{
df::creature_raw* raw = world->raws.creatures.all[id];
if (raw)
return raw->general_child_name[0];
}
return "";
}

@ -77,7 +77,7 @@ set_source_files_properties( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE )
#dfhack_plugin(add-spatter add-spatter.cpp)
dfhack_plugin(autobutcher autobutcher.cpp LINK_LIBRARIES lua)
dfhack_plugin(autochop autochop.cpp LINK_LIBRARIES lua)
#dfhack_plugin(autoclothing autoclothing.cpp)
dfhack_plugin(autoclothing autoclothing.cpp)
dfhack_plugin(autodump autodump.cpp)
dfhack_plugin(autofarm autofarm.cpp)
#dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_static)

@ -13,7 +13,17 @@
#include "modules/Materials.h"
#include "modules/Units.h"
#include "modules/World.h"
#include "modules/Translation.h"
#include "df/item.h"
#include "df/item_actual.h"
#include "df/item_crafted.h"
#include "df/item_constructed.h"
#include "df/item_armorst.h"
#include "df/item_glovesst.h"
#include "df/item_shoesst.h"
#include "df/item_helmst.h"
#include "df/item_pantsst.h"
#include "df/itemdef_armorst.h"
#include "df/itemdef_glovesst.h"
#include "df/itemdef_shoesst.h"
@ -55,6 +65,9 @@ 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 void generate_report(color_ostream& out);
static bool isAvailableItem(df::item* item);
std::vector<ClothingRequirement>clothingOrders;
@ -366,6 +379,12 @@ command_result autoclothing(color_ostream &out, std::vector <std::string> & para
}
return CR_OK;
}
else if (parameters.size() == 1 && parameters[0] == "report")
{
CoreSuspender suspend;
generate_report(out);
return CR_OK;
}
else if (parameters.size() < 2 || parameters.size() > 3)
{
out << "Wrong number of arguments." << endl;
@ -663,3 +682,200 @@ static void save_state(color_ostream &out)
item.val() = clothingOrders[i].Serialize();
}
}
static void list_unit_counts(color_ostream& out, std::map<int, int>& unitList)
{
for (const auto& race : unitList)
{
if (race.second == 1)
out << " 1 " << Units::getRaceReadableNameById(race.first) << endl;
else
out << " " << race.second << " " << Units::getRaceNamePluralById(race.first) << endl;
}
}
static bool isAvailableItem(df::item* item)
{
if (item->flags.bits.in_job)
return false;
if (item->flags.bits.hostile)
return false;
if (item->flags.bits.in_building)
return false;
if (item->flags.bits.in_building)
return false;
if (item->flags.bits.encased)
return false;
if (item->flags.bits.foreign)
return false;
if (item->flags.bits.trader)
return false;
if (item->flags.bits.owned)
return false;
if (item->flags.bits.artifact)
return false;
if (item->flags.bits.forbid)
return false;
if (item->flags.bits.dump)
return false;
if (item->flags.bits.on_fire)
return false;
if (item->flags.bits.melt)
return false;
if (item->flags.bits.hidden)
return false;
if (item->getWear() > 1)
return false;
if (!item->isClothing())
return false;
return true;
}
static void generate_report(color_ostream& out)
{
std::map<int, int> fullUnitList;
std::map<int, int> missingArmor;
std::map<int, int> missingShoes;
std::map<int, int> missingHelms;
std::map<int, int> missingGloves;
std::map<int, int> missingPants;
for (df::unit* unit : world->units.active)
{
if (!Units::isCitizen(unit))
continue;
fullUnitList[unit->race]++;
int numArmor = 0, numShoes = 0, numHelms = 0, numGloves = 0, numPants = 0;
for (auto itemId : unit->owned_items)
{
auto item = Items::findItemByID(itemId);
if (item->getWear() >= 1)
continue;
switch (item->getType())
{
case df::item_type::ARMOR:
numArmor++;
break;
case df::item_type::SHOES:
numShoes++;
break;
case df::item_type::HELM:
numHelms++;
break;
case df::item_type::GLOVES:
numGloves++;
break;
case df::item_type::PANTS:
numPants++;
break;
default:
break;
}
}
if (numArmor == 0)
missingArmor[unit->race]++;
if (numShoes < 2)
missingShoes[unit->race]++;
if (numHelms == 0)
missingHelms[unit->race]++;
if (numGloves < 2)
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;
}
if (missingArmor.size() + missingShoes.size() + missingHelms.size() + missingGloves.size() + missingPants.size() == 0)
{
out << "Everybody has a full set of clothes to wear, congrats!" << endl;
return;
}
else
{
if (missingArmor.size())
{
out << "Following units need new bodywear:" << endl;
list_unit_counts(out, missingArmor);
}
if (missingShoes.size())
{
out << "Following units need new shoes:" << endl;
list_unit_counts(out, missingShoes);
}
if (missingHelms.size())
{
out << "Following units need new headwear:" << endl;
list_unit_counts(out, missingHelms);
}
if (missingGloves.size())
{
out << "Following units need new handwear:" << endl;
list_unit_counts(out, missingGloves);
}
if (missingPants.size())
{
out << "Following units need new legwear:" << endl;
list_unit_counts(out, missingPants);
}
}
std::map<int, int> availableArmor;
for (auto armor : world->items.other.ARMOR)
{
if (!isAvailableItem(armor))
continue;
availableArmor[armor->maker_race]++;
}
if (availableArmor.size())
{
out << "We have available bodywear for:" << endl;
list_unit_counts(out, availableArmor);
}
std::map<int, int> availableShoes;
for (auto shoe : world->items.other.SHOES)
{
if (!isAvailableItem(shoe))
continue;
availableShoes[shoe->maker_race]++;
}
if (availableShoes.size())
{
out << "We have available footwear for:" << endl;
list_unit_counts(out, availableShoes);
}
std::map<int, int> availableHelms;
for (auto helm : world->items.other.HELM)
{
if (!isAvailableItem(helm))
continue;
availableHelms[helm->maker_race]++;
}
if (availableHelms.size())
{
out << "We have available headwear for:" << endl;
list_unit_counts(out, availableHelms);
}
std::map<int, int> availableGloves;
for (auto glove : world->items.other.HELM)
{
if (!isAvailableItem(glove))
continue;
availableGloves[glove->maker_race]++;
}
if (availableGloves.size())
{
out << "We have available handwear for:" << endl;
list_unit_counts(out, availableGloves);
}
std::map<int, int> availablePants;
for (auto pants : world->items.other.HELM)
{
if (!isAvailableItem(pants))
continue;
availablePants[pants->maker_race]++;
}
if (availablePants.size())
{
out << "We have available legwear for:" << endl;
list_unit_counts(out, availablePants);
}
}