I significantly restructured the assignment algorithm to eliminate the overproduction issues in #3093

#3103 is addressed by excluding units that don't have the caste-level EQUIPS flag

closes #3093
closes #3103
develop
Kelly Kinkade 2023-03-27 00:07:40 -05:00
parent 428f7572b1
commit 4a173aac99
2 changed files with 75 additions and 78 deletions

@ -44,7 +44,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- `buildingplan`: you can no longer designate constructions on tiles with magma or deep water
- `buildingplan`: fixed material filter getting lost for planning buildings on save/reload
- `buildingplan`: respect building size limits (e.g. roads and bridges cannot be more than 31 tiles in any dimension)
- `tailor`: now properly discriminates between dyed and undyed cloth, no longer defaults to using adamantine, and properly tracks material requirements for already queued orders
- `tailor`: now properly discriminates between dyed and undyed cloth, no longer defaults to using adamantine, properly tracks material requirements for already queued orders, skips units who can't wear clothes, and hopefully won't over-order items anymore
## Misc Improvements
- `buildingplan`: filters and global settings are now ignored when manually choosing items for a building

@ -144,13 +144,15 @@ static struct BadFlags {
} badFlags;
class Tailor {
private:
std::map<std::pair<df::item_type, int>, int> available; // key is item type & size
std::map<std::pair<df::item_type, int>, int> needed; // same
std::map<std::pair<df::item_type, int>, int> queued; // same
private:
std::map<int, int> sizes; // this maps body size to races
std::map<std::tuple<df::job_type, int, int>, int> orders; // key is item type, item subtype, size
std::map<std::pair<df::item_type, int>, int> available;
std::map<std::pair<df::item_type, int>, int> needed;
std::map<std::tuple<df::job_type, int, int>, int> orders;
std::map<MatType, int> supply;
std::map<MatType, int> reserves;
@ -169,50 +171,34 @@ public:
{
available.clear();
needed.clear();
queued.clear();
sizes.clear();
orders.clear();
supply.clear();
orders.clear();
}
void scan_clothing()
{
if (!inventory_sanity_checking)
for (auto i : world->items.other[df::items_other_id::ANY_GENERIC37]) // GENERIC37 is "nontattered clothing"
{
for (auto i : world->items.other[df::items_other_id::ANY_GENERIC37]) // GENERIC37 is "clothing"
if (i->flags.whole & badFlags.whole)
{
if (i->flags.whole & badFlags.whole)
continue;
if (i->getWear() >= 1)
continue;
df::item_type t = i->getType();
int size = world->raws.creatures.all[i->getMakerRace()]->adultsize;
available[std::make_pair(t, size)] += 1;
continue;
}
}
else
{
auto& l = world->items.other[df::items_other_id::ANY_GENERIC37];
for (auto i : world->items.other[df::items_other_id::IN_PLAY])
{
if (i->flags.whole & badFlags.whole)
continue;
if (!i->isClothing())
continue;
if (std::find(std::begin(l), std::end(l), i) == std::end(l))
{
DEBUG(cycle).print("tailor: clothing item %d missing from GENERIC37 list\n", i->id);
}
if (i->getWear() >= 1)
continue;
df::item_type t = i->getType();
int size = world->raws.creatures.all[i->getMakerRace()]->adultsize;
if (i->getWear() >= 1)
continue;
df::item_type t = i->getType();
int size = world->raws.creatures.all[i->getMakerRace()]->adultsize;
available[std::make_pair(t, size)] += 1;
}
available[std::make_pair(t, size)] += 1;
}
for (auto& i : available)
{
df::item_type t;
int size;
std::tie(t, size) = i.first;
DEBUG(cycle).print("tailor: %d %s of size %d found\n",
i.second, ENUM_KEY_STR(item_type, t).c_str(), size);
}
}
@ -273,14 +259,13 @@ public:
if (!Units::isOwnCiv(u) ||
!Units::isOwnGroup(u) ||
!Units::isActive(u) ||
Units::isBaby(u))
continue; // skip units we don't control
Units::isBaby(u) ||
!Units::casteFlagSet(u->race, u->caste, df::enums::caste_raw_flags::EQUIPS))
continue; // skip units we don't control or that can't wear clothes
std::set <df::item_type> wearing;
wearing.clear();
std::set <df::item_type> ordered;
std::deque<df::item*> worn;
worn.clear();
for (auto inv : u->inventory)
{
@ -295,33 +280,37 @@ public:
int usize = world->raws.creatures.all[u->race]->adultsize;
sizes[usize] = u->race;
for (auto ty : std::set<df::item_type>{ df::item_type::ARMOR, df::item_type::PANTS, df::item_type::SHOES })
{
if (wearing.count(ty) == 0)
{
TRACE(cycle).print("tailor: one %s of size %d needed to cover %s\n",
ENUM_KEY_STR(item_type, ty).c_str(),
usize,
Translation::TranslateName(&u->name, false).c_str());
needed[std::make_pair(ty, usize)] += 1;
}
}
for (auto w : worn)
{
auto ty = w->getType();
auto oo = itemTypeMap.find(ty);
if (oo == itemTypeMap.end())
{
continue;
}
const df::job_type o = oo->second;
int isize = world->raws.creatures.all[w->getMakerRace()]->adultsize;
std::string description;
w->getItemDescription(&description, 0);
if (available[std::make_pair(ty, usize)] > 0)
bool allocated = false;
if (wearing.count(ty) == 0)
{
if (available[std::make_pair(ty, usize)] > 0)
{
available[std::make_pair(ty, usize)] -= 1;
DEBUG(cycle).print("tailor: allocating a %s (size %d) to %s\n",
ENUM_KEY_STR(item_type, ty).c_str(), usize,
Translation::TranslateName(&u->name, false).c_str());
wearing.insert(ty);
}
else if (ordered.count(ty) == 0)
{
DEBUG(cycle).print ("tailor: %s (size %d) worn by %s (size %d) needs replacement, but none available\n",
description.c_str(), isize,
Translation::TranslateName(&u->name, false).c_str(), usize);
needed[std::make_pair(ty, usize)] += 1;
ordered.insert(ty);
}
}
if (wearing.count(ty) > 0)
{
if (w->flags.bits.owned)
{
@ -335,23 +324,21 @@ public:
);
}
if (wearing.count(ty) == 0)
{
DEBUG(cycle).print("tailor: allocating a %s (size %d) to %s\n",
ENUM_KEY_STR(item_type, ty).c_str(), usize,
Translation::TranslateName(&u->name, false).c_str());
available[std::make_pair(ty, usize)] -= 1;
}
if (w->getWear() > 1)
w->flags.bits.dump = true;
}
else
}
for (auto ty : std::set<df::item_type>{ df::item_type::ARMOR, df::item_type::PANTS, df::item_type::SHOES })
{
if (wearing.count(ty) == 0 && ordered.count(ty) == 0)
{
DEBUG(cycle).print ("tailor: %s (size %d) worn by %s (size %d) needs replacement, but none available\n",
description.c_str(), isize,
Translation::TranslateName(&u->name, false).c_str(), usize);
orders[std::make_tuple(o, w->getSubtype(), usize)] += 1;
TRACE(cycle).print("tailor: one %s of size %d needed to cover %s\n",
ENUM_KEY_STR(item_type, ty).c_str(),
usize,
Translation::TranslateName(&u->name, false).c_str());
needed[std::make_pair(ty, usize)] += 1;
}
}
}
@ -367,6 +354,9 @@ public:
int size = a.first.second;
int count = a.second;
if (count <= 0)
continue;
int sub = 0;
std::vector<int16_t> v;
@ -432,7 +422,14 @@ public:
int size = world->raws.creatures.all[race]->adultsize;
orders[std::make_tuple(o->job_type, sub, size)] -= o->amount_left;
auto tt = jobTypeMap.find(o->job_type);
if (tt == jobTypeMap.end())
{
continue;
}
needed[std::make_pair(tt->second, size)] -= o->amount_left;
TRACE(cycle).print("tailor: existing order for %d %s of size %d detected\n",
o->amount_left,
ENUM_KEY_STR(job_type, o->job_type).c_str(),
@ -576,8 +573,8 @@ public:
scan_clothing();
scan_materials();
scan_replacements();
create_orders();
scan_existing_orders();
create_orders();
return place_orders();
}