tailor: add support for user-specified material list

Add support for user-specified material list. Also refactor, fix some mostly-innocuous bugs

Resolves #1911.
develop
Kelly Kinkade 2021-08-08 09:53:32 -05:00
parent 40fc3dd110
commit 73107fb21b
1 changed files with 465 additions and 314 deletions

@ -41,44 +41,92 @@ DFHACK_PLUGIN_IS_ENABLED(enabled);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui);
const char *tagline = "Allow the bookkeeper to queue jobs to keep dwarfs in adequate clothing."; const char* tagline = "Allow the bookkeeper to queue jobs to keep dwarfs in adequate clothing.";
const char *usage = ( const char* usage = (
" tailor enable\n" " tailor enable\n"
" Enable the plugin.\n" " Enable the plugin.\n"
" tailor disable\n" " tailor disable\n"
" Disable the plugin.\n" " Disable the plugin.\n"
" tailor status\n" " tailor status\n"
" Display plugin status\n" " Display plugin status\n"
" tailor materials ...\n"
" for example: tailor materials silk cloth yarn leather\n"
" Set allowed material list to the specified list.\n"
" The example sets the list to silk, cloth, yarn, leather, in that order, which is the default.\n"
"\n" "\n"
"Whenever the bookkeeper updates stockpile records, this plugin will scan every unit in the fort,\n" "Whenever the bookkeeper updates stockpile records, this plugin will scan every unit in the fort,\n"
"count up the number that are worn, and then order enough more made to replace all worn items.\n" "count up the number that are worn, and then order enough more made to replace all worn items.\n"
"If there are enough replacement items in inventory to replace all worn items, the units wearing them\n" "If there are enough replacement items in inventory to replace all worn items, the units wearing them\n"
"will have the worn items confiscated (in the same manner as the _cleanowned_ plugin) so that they'll\n" "will have the worn items confiscated (in the same manner as the _cleanowned_ plugin) so that they'll\n"
"reeequip with replacement items.\n" "reeequip with replacement items.\n"
); );
class Tailor {
// ARMOR, SHOES, HELM, GLOVES, PANTS
// ah, if only STL had a bimap
private:
const map<df::job_type, df::item_type> jobTypeMap = {
{ df::job_type::MakeArmor, df::item_type::ARMOR },
{ df::job_type::MakePants, df::item_type::PANTS },
{ df::job_type::MakeHelm, df::item_type::HELM },
{ df::job_type::MakeGloves, df::item_type::GLOVES },
{ df::job_type::MakeShoes, df::item_type::SHOES }
};
const map<df::item_type, df::job_type> itemTypeMap = {
{ df::item_type::ARMOR, df::job_type::MakeArmor },
{ df::item_type::PANTS, df::job_type::MakePants },
{ df::item_type::HELM, df::job_type::MakeHelm },
{ df::item_type::GLOVES, df::job_type::MakeGloves },
{ df::item_type::SHOES, df::job_type::MakeShoes }
};
#define F(x) df::item_flags::mask_##x
const df::item_flags bad_flags = {
(
F(dump) | F(forbid) | F(garbage_collect) |
F(hostile) | F(on_fire) | F(rotten) | F(trader) |
F(in_building) | F(construction) | F(owned)
)
#undef F
};
class MatType {
public:
std::string name;
df::job_material_category job_material;
df::armor_general_flags armor_flag;
bool operator==(const MatType& m) const
{
return name == m.name;
}
// ARMOR, SHOES, HELM, GLOVES, PANTS // operator< is required to use this as a std::map key
bool operator<(const MatType& m) const
{
return name < m.name;
}
// ah, if only STL had a bimap MatType(std::string& n, df::job_material_category jm, df::armor_general_flags af)
: name(n), job_material(jm), armor_flag(af) {};
MatType(const char* n, df::job_material_category jm, df::armor_general_flags af)
: name(std::string(n)), job_material(jm), armor_flag(af) {};
static map<df::job_type, df::item_type> jobTypeMap = { };
{ df::job_type::MakeArmor, df::item_type::ARMOR },
{ df::job_type::MakePants, df::item_type::PANTS },
{ df::job_type::MakeHelm, df::item_type::HELM },
{ df::job_type::MakeGloves, df::item_type::GLOVES },
{ df::job_type::MakeShoes, df::item_type::SHOES }
};
static map<df::item_type, df::job_type> itemTypeMap = { const MatType
{ df::item_type::ARMOR, df::job_type::MakeArmor }, M_SILK = MatType("silk", df::job_material_category::mask_silk, df::armor_general_flags::SOFT),
{ df::item_type::PANTS, df::job_type::MakePants }, M_CLOTH = MatType("cloth", df::job_material_category::mask_cloth, df::armor_general_flags::SOFT),
{ df::item_type::HELM, df::job_type::MakeHelm}, M_YARN = MatType("yarn", df::job_material_category::mask_yarn, df::armor_general_flags::SOFT),
{ df::item_type::GLOVES, df::job_type::MakeGloves}, M_LEATHER = MatType("leather", df::job_material_category::mask_leather, df::armor_general_flags::LEATHER);
{ df::item_type::SHOES, df::job_type::MakeShoes}
}; std::list<MatType> all_materials = { M_SILK, M_CLOTH, M_YARN, M_LEATHER };
void do_scan(color_ostream& out)
{
map<pair<df::item_type, int>, int> available; // key is item type & size map<pair<df::item_type, int>, int> available; // key is item type & size
map<pair<df::item_type, int>, int> needed; // same map<pair<df::item_type, int>, int> needed; // same
map<pair<df::item_type, int>, int> queued; // same map<pair<df::item_type, int>, int> queued; // same
@ -87,335 +135,421 @@ void do_scan(color_ostream& out)
map<tuple<df::job_type, int, int>, int> orders; // key is item type, item subtype, size map<tuple<df::job_type, int, int>, int> orders; // key is item type, item subtype, size
df::item_flags bad_flags; std::map<MatType, int> supply;
bad_flags.whole = 0;
#define F(x) bad_flags.bits.x = true;
F(dump); F(forbid); F(garbage_collect);
F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(construction); F(owned);
#undef F
available.empty(); color_ostream* out;
needed.empty();
queued.empty();
orders.empty();
int silk = 0, yarn = 0, cloth = 0, leather = 0; std::list<MatType> material_order = { M_SILK, M_CLOTH, M_YARN, M_LEATHER };
std::map<MatType, int> reserves;
// scan for useable clothing int default_reserve = 10;
for (auto i : world->items.other[df::items_other_id::ANY_GENERIC37]) // GENERIC37 is "clothing" void reset()
{ {
if (i->flags.whole & bad_flags.whole) available.clear();
continue; needed.clear();
if (i->flags.bits.owned) queued.clear();
continue; sizes.clear();
if (i->getWear() >= 1) orders.clear();
continue; supply.clear();
df::item_type t = i->getType();
int size = world->raws.creatures.all[i->getMakerRace()]->adultsize;
available[make_pair(t, size)] += 1;
} }
// scan for clothing raw materials void scan_clothing()
for (auto i : world->items.other[df::items_other_id::CLOTH])
{ {
if (i->flags.whole & bad_flags.whole) for (auto i : world->items.other[df::items_other_id::ANY_GENERIC37]) // GENERIC37 is "clothing"
continue;
if (!i->hasImprovements()) // only count dyed
continue;
MaterialInfo mat(i);
int ss = i->getStackSize();
if (mat.material)
{ {
if (mat.material->flags.is_set(df::material_flags::SILK)) if (i->flags.whole & bad_flags.whole)
silk += ss; continue;
else if (mat.material->flags.is_set(df::material_flags::THREAD_PLANT)) if (i->flags.bits.owned)
cloth += ss; continue;
else if (mat.material->flags.is_set(df::material_flags::YARN)) if (i->getWear() >= 1)
yarn += ss; continue;
} df::item_type t = i->getType();
} int size = world->raws.creatures.all[i->getMakerRace()]->adultsize;
for (auto i : world->items.other[df::items_other_id::SKIN_TANNED]) available[make_pair(t, size)] += 1;
{ }
if (i->flags.whole & bad_flags.whole)
continue;
leather += i->getStackSize();
} }
out.print("available: silk %d yarn %d cloth %d leather %d\n", silk, yarn, cloth, leather); void scan_materials()
// scan for units who need replacement clothing
for (auto u : world->units.active)
{ {
if (!Units::isOwnCiv(u) || for (auto i : world->items.other[df::items_other_id::CLOTH])
!Units::isOwnGroup(u) || {
!Units::isActive(u) || if (i->flags.whole & bad_flags.whole)
Units::isBaby(u)) continue;
continue; // skip units we don't control if (!i->hasImprovements()) // only count dyed
continue;
set <df::item_type> wearing; MaterialInfo mat(i);
wearing.empty(); int ss = i->getStackSize();
deque<df::item*> worn; if (mat.material)
worn.empty(); {
if (mat.material->flags.is_set(df::material_flags::SILK))
supply[M_SILK] += ss;
else if (mat.material->flags.is_set(df::material_flags::THREAD_PLANT))
supply[M_CLOTH] += ss;
else if (mat.material->flags.is_set(df::material_flags::YARN))
supply[M_YARN] += ss;
}
}
for (auto inv : u->inventory) for (auto i : world->items.other[df::items_other_id::SKIN_TANNED])
{ {
if (inv->mode != df::unit_inventory_item::Worn) if (i->flags.whole & bad_flags.whole)
continue; continue;
if (inv->item->getWear() > 0) supply[M_LEATHER] += i->getStackSize();
worn.push_back(inv->item);
else
wearing.insert(inv->item->getType());
} }
int size = world->raws.creatures.all[u->race]->adultsize; out->print("tailor: available silk %d yarn %d cloth %d leather %d\n", supply[M_SILK], supply[M_YARN], supply[M_CLOTH], supply[M_LEATHER]);
sizes[size] = u->race; }
for (auto ty : set<df::item_type>{ df::item_type::ARMOR, df::item_type::PANTS, df::item_type::SHOES }) void scan_replacements()
{
for (auto u : world->units.active)
{ {
if (wearing.count(ty) == 0) if (!Units::isOwnCiv(u) ||
needed[make_pair(ty, size)] += 1; !Units::isOwnGroup(u) ||
} !Units::isActive(u) ||
Units::isBaby(u))
continue; // skip units we don't control
for (auto w : worn) set <df::item_type> wearing;
{ wearing.clear();
auto ty = w->getType();
auto oo = itemTypeMap.find(ty);
if (oo == itemTypeMap.end())
continue;
df::job_type o = oo->second;
int size = world->raws.creatures.all[w->getMakerRace()]->adultsize; deque<df::item*> worn;
std::string description; worn.clear();
w->getItemDescription(&description, 0);
if (available[make_pair(ty, size)] > 0) for (auto inv : u->inventory)
{ {
if (w->flags.bits.owned) if (inv->mode != df::unit_inventory_item::Worn)
{ continue;
bool confiscated = Items::setOwner(w, NULL); if (inv->item->getWear() > 0)
worn.push_back(inv->item);
out.print( else
"%s %s from %s.\n", wearing.insert(inv->item->getType());
(confiscated ? "Confiscated" : "Could not confiscate"), }
description.c_str(),
Translation::TranslateName(&u->name, false).c_str()
);
}
if (wearing.count(ty) == 0) int size = world->raws.creatures.all[u->race]->adultsize;
available[make_pair(ty, size)] -= 1; sizes[size] = u->race;
if (w->getWear() > 1) for (auto ty : set<df::item_type>{ df::item_type::ARMOR, df::item_type::PANTS, df::item_type::SHOES })
w->flags.bits.dump = true; {
if (wearing.count(ty) == 0)
needed[make_pair(ty, size)] += 1;
} }
else
for (auto w : worn)
{ {
// out.print("%s worn by %s needs replacement\n", auto ty = w->getType();
// description.c_str(), auto o = itemTypeMap.at(ty);
// Translation::TranslateName(&u->name, false).c_str()
// ); int size = world->raws.creatures.all[w->getMakerRace()]->adultsize;
orders[make_tuple(o, w->getSubtype(), size)] += 1; std::string description;
w->getItemDescription(&description, 0);
if (available[make_pair(ty, size)] > 0)
{
if (w->flags.bits.owned)
{
bool confiscated = Items::setOwner(w, NULL);
out->print(
"tailor: %s %s from %s.\n",
(confiscated ? "confiscated" : "could not confiscate"),
description.c_str(),
Translation::TranslateName(&u->name, false).c_str()
);
}
if (wearing.count(ty) == 0)
available[make_pair(ty, size)] -= 1;
if (w->getWear() > 1)
w->flags.bits.dump = true;
}
else
{
// out->print("%s worn by %s needs replacement\n",
// description.c_str(),
// Translation::TranslateName(&u->name, false).c_str()
// );
orders[make_tuple(o, w->getSubtype(), size)] += 1;
}
} }
} }
} }
auto entity = world->entities.all[ui->civ_id]; void create_orders()
for (auto a : needed)
{ {
df::item_type ty = a.first.first; auto entity = world->entities.all[ui->civ_id];
int size = a.first.second;
int count = a.second; for (auto& a : needed)
{
int sub = 0; df::item_type ty = a.first.first;
vector<int16_t> v; int size = a.first.second;
int count = a.second;
switch (ty) {
case df::item_type::ARMOR: v = entity->resources.armor_type; break; int sub = 0;
case df::item_type::GLOVES: v = entity->resources.gloves_type; break; vector<int16_t> v;
case df::item_type::HELM: v = entity->resources.helm_type; break;
case df::item_type::PANTS: v = entity->resources.pants_type; break;
case df::item_type::SHOES: v = entity->resources.shoes_type; break;
default: break;
}
for (auto vv : v) {
bool isClothing = false;
switch (ty) { switch (ty) {
case df::item_type::ARMOR: isClothing = world->raws.itemdefs.armor[vv] ->armorlevel == 0; break; case df::item_type::ARMOR: v = entity->resources.armor_type; break;
case df::item_type::GLOVES: isClothing = world->raws.itemdefs.gloves[vv]->armorlevel == 0; break; case df::item_type::GLOVES: v = entity->resources.gloves_type; break;
case df::item_type::HELM: isClothing = world->raws.itemdefs.helms[vv] ->armorlevel == 0; break; case df::item_type::HELM: v = entity->resources.helm_type; break;
case df::item_type::PANTS: isClothing = world->raws.itemdefs.pants[vv] ->armorlevel == 0; break; case df::item_type::PANTS: v = entity->resources.pants_type; break;
case df::item_type::SHOES: isClothing = world->raws.itemdefs.shoes[vv] ->armorlevel == 0; break; case df::item_type::SHOES: v = entity->resources.shoes_type; break;
default: break; default: break;
} }
if (isClothing)
{ for (auto vv : v) {
sub = vv; bool isClothing = false;
break; switch (ty) {
case df::item_type::ARMOR: isClothing = world->raws.itemdefs.armor[vv]->armorlevel == 0; break;
case df::item_type::GLOVES: isClothing = world->raws.itemdefs.gloves[vv]->armorlevel == 0; break;
case df::item_type::HELM: isClothing = world->raws.itemdefs.helms[vv]->armorlevel == 0; break;
case df::item_type::PANTS: isClothing = world->raws.itemdefs.pants[vv]->armorlevel == 0; break;
case df::item_type::SHOES: isClothing = world->raws.itemdefs.shoes[vv]->armorlevel == 0; break;
default: break;
}
if (isClothing)
{
sub = vv;
break;
}
} }
}
orders[make_tuple(itemTypeMap[ty], sub, size)] += count; const df::job_type j = itemTypeMap.at(ty);
orders[make_tuple(j, sub, size)] += count;
}
} }
// scan orders void scan_existing_orders()
for (auto o : world->manager_orders)
{ {
auto f = jobTypeMap.find(o->job_type); for (auto o : world->manager_orders)
if (f == jobTypeMap.end()) {
continue; auto f = jobTypeMap.find(o->job_type);
if (f == jobTypeMap.end())
continue;
auto sub = o->item_subtype; auto sub = o->item_subtype;
int race = o->hist_figure_id; int race = o->hist_figure_id;
if (race == -1) if (race == -1)
continue; // -1 means that the race of the worker will determine the size made; we must ignore these jobs continue; // -1 means that the race of the worker will determine the size made; we must ignore these jobs
int size = world->raws.creatures.all[race]->adultsize; int size = world->raws.creatures.all[race]->adultsize;
orders[make_tuple(o->job_type, sub, size)] -= o->amount_left; orders[make_tuple(o->job_type, sub, size)] -= o->amount_left;
} }
// place orders }
for (auto o : orders) void place_orders()
{ {
df::job_type ty; auto entity = world->entities.all[ui->civ_id];
int sub;
int size;
tie(ty, sub, size) = o.first; for (auto& o : orders)
int count = o.second;
if (count > 0)
{ {
vector<int16_t> v; df::job_type ty;
BitArray<df::armor_general_flags>* fl; int sub;
string name_s, name_p; int size;
switch (ty) { tie(ty, sub, size) = o.first;
case df::job_type::MakeArmor: int count = o.second;
name_s = world->raws.itemdefs.armor[sub]->name;
name_p = world->raws.itemdefs.armor[sub]->name_plural;
v = entity->resources.armor_type;
fl = &world->raws.itemdefs.armor[sub]->props.flags;
break;
case df::job_type::MakeGloves:
name_s = world->raws.itemdefs.gloves[sub]->name;
name_p = world->raws.itemdefs.gloves[sub]->name_plural;
v = entity->resources.gloves_type;
fl = &world->raws.itemdefs.gloves[sub]->props.flags;
break;
case df::job_type::MakeHelm:
name_s = world->raws.itemdefs.helms[sub]->name;
name_p = world->raws.itemdefs.helms[sub]->name_plural;
v = entity->resources.helm_type;
fl = &world->raws.itemdefs.helms[sub]->props.flags;
break;
case df::job_type::MakePants:
name_s = world->raws.itemdefs.pants[sub]->name;
name_p = world->raws.itemdefs.pants[sub]->name_plural;
v = entity->resources.pants_type;
fl = &world->raws.itemdefs.pants[sub]->props.flags;
break;
case df::job_type::MakeShoes:
name_s = world->raws.itemdefs.shoes[sub]->name;
name_p = world->raws.itemdefs.shoes[sub]->name_plural;
v = entity->resources.shoes_type;
fl = &world->raws.itemdefs.shoes[sub]->props.flags;
break;
default:
break;
}
bool can_make = false; if (count > 0)
for (auto vv : v)
{ {
if (vv == sub) vector<int16_t> v;
{ BitArray<df::armor_general_flags>* fl;
can_make = true; string name_s, name_p;
switch (ty) {
case df::job_type::MakeArmor:
v = entity->resources.armor_type;
name_s = world->raws.itemdefs.armor[sub]->name;
name_p = world->raws.itemdefs.armor[sub]->name_plural;
fl = &world->raws.itemdefs.armor[sub]->props.flags;
break;
case df::job_type::MakeGloves:
name_s = world->raws.itemdefs.gloves[sub]->name;
name_p = world->raws.itemdefs.gloves[sub]->name_plural;
v = entity->resources.gloves_type;
fl = &world->raws.itemdefs.gloves[sub]->props.flags;
break;
case df::job_type::MakeHelm:
name_s = world->raws.itemdefs.helms[sub]->name;
name_p = world->raws.itemdefs.helms[sub]->name_plural;
v = entity->resources.helm_type;
fl = &world->raws.itemdefs.helms[sub]->props.flags;
break;
case df::job_type::MakePants:
name_s = world->raws.itemdefs.pants[sub]->name;
name_p = world->raws.itemdefs.pants[sub]->name_plural;
v = entity->resources.pants_type;
fl = &world->raws.itemdefs.pants[sub]->props.flags;
break;
case df::job_type::MakeShoes:
name_s = world->raws.itemdefs.shoes[sub]->name;
name_p = world->raws.itemdefs.shoes[sub]->name_plural;
v = entity->resources.shoes_type;
fl = &world->raws.itemdefs.shoes[sub]->props.flags;
break;
default:
break; break;
} }
}
if (!can_make) bool can_make = std::find(v.begin(), v.end(), sub) != v.end();
{
out.print("Cannot make %s, skipped\n", name_p.c_str());
continue; // this civilization does not know how to make this item, so sorry
}
switch (ty) { if (!can_make)
case df::item_type::ARMOR: break; {
case df::item_type::GLOVES: break; out->print("tailor: civilization cannot make %s, skipped\n", name_p.c_str());
case df::item_type::HELM: break; continue;
case df::item_type::PANTS: break; }
case df::item_type::SHOES: break;
default: break; for (auto& m : material_order)
{
if (count <= 0)
break;
auto r = reserves.find(m);
int res = (r == reserves.end()) ? default_reserve : r->second;
if (supply[m] > res && fl->is_set(m.armor_flag)) {
int c = count;
if (supply[m] < count + res)
c = supply[m] - res;
supply[m] -= c;
auto order = new df::manager_order;
order->job_type = ty;
order->item_type = df::item_type::NONE;
order->item_subtype = sub;
order->mat_type = -1;
order->mat_index = -1;
order->amount_left = c;
order->amount_total = c;
order->status.bits.validated = false;
order->status.bits.active = false;
order->id = world->manager_order_next_id++;
order->hist_figure_id = sizes[size];
order->material_category = m.job_material;
world->manager_orders.push_back(order);
out->print("tailor: added order #%d for %d %s %s, sized for %s\n",
order->id,
c,
bitfield_to_string(order->material_category).c_str(),
(c > 1) ? name_p.c_str() : name_s.c_str(),
world->raws.creatures.all[order->hist_figure_id]->name[1].c_str()
);
count -= c;
}
}
} }
}
}
df::job_material_category mat; public:
void do_scan(color_ostream& o)
{
out = &o;
if (silk > count + 10 && fl->is_set(df::armor_general_flags::SOFT)) { reset();
mat.whole = df::job_material_category::mask_silk;
silk -= count; // scan for useable clothing
}
else if (cloth > count + 10 && fl->is_set(df::armor_general_flags::SOFT)) { scan_clothing();
mat.whole = df::job_material_category::mask_cloth;
cloth -= count; // scan for clothing raw materials
scan_materials();
// scan for units who need replacement clothing
scan_replacements();
// create new orders
create_orders();
// scan existing orders and subtract
scan_existing_orders();
// place orders
place_orders();
}
public:
command_result set_materials(color_ostream& out, vector<string>& parameters)
{
list<MatType> newmat;
newmat.clear();
for (auto m = parameters.begin() + 1; m != parameters.end(); m++)
{
auto nameMatch = [m](MatType& m1) { return *m == m1.name; };
auto mm = std::find_if(all_materials.begin(), all_materials.end(), nameMatch);
if (mm == all_materials.end())
{
out.print("tailor: material %s not recognized\n", m->c_str());
return CR_WRONG_USAGE;
} }
else if (yarn > count + 10 && fl->is_set(df::armor_general_flags::SOFT)) { else {
mat.whole = df::job_material_category::mask_yarn; newmat.push_back(*mm);
yarn -= count;
} }
else if (leather > count + 10 && fl->is_set(df::armor_general_flags::LEATHER)) { }
mat.whole = df::job_material_category::mask_leather;
leather -= count; material_order = newmat;
out.print("tailor: material list set to %s\n", get_material_list().c_str());
return CR_OK;
}
public:
std::string get_material_list()
{
std::string s;
for (const auto& m : material_order)
{
if (!s.empty()) s += ", ";
s += m.name;
}
return s;
}
public:
void process(color_ostream& out)
{
bool found = false;
for (df::job_list_link* link = &world->jobs.list; link != NULL; link = link->next)
{
if (link->item == NULL) continue;
if (link->item->job_type == df::enums::job_type::UpdateStockpileRecords)
{
found = true;
break;
} }
else // not enough appropriate material available }
continue;
auto order = new df::manager_order(); if (found)
order->job_type = ty; {
order->item_type = df::item_type::NONE; do_scan(out);
order->item_subtype = sub;
order->mat_type = -1;
order->mat_index = -1;
order->amount_left = count;
order->amount_total = count;
order->status.bits.validated = false;
order->status.bits.active = false;
order->id = world->manager_order_next_id++;
order->hist_figure_id = sizes[size];
order->material_category = mat;
world->manager_orders.push_back(order);
out.print("Added order #%d for %d %s %s (sized for %s)\n",
order->id,
count,
bitfield_to_string(order->material_category).c_str(),
(count > 1) ? name_p.c_str() : name_s.c_str(),
world->raws.creatures.all[order->hist_figure_id]->name[1].c_str()
);
} }
} }
} };
static std::unique_ptr<Tailor> tailor_instance;
#define DELTA_TICKS 600 #define DELTA_TICKS 600
DFhackCExport command_result plugin_onupdate(color_ostream &out) DFhackCExport command_result plugin_onupdate(color_ostream& out)
{ {
if (!enabled) if (!enabled || !tailor_instance)
return CR_OK; return CR_OK;
if (!Maps::IsValid()) if (!Maps::IsValid())
@ -427,64 +561,76 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
if (world->frame_counter % DELTA_TICKS != 0) if (world->frame_counter % DELTA_TICKS != 0)
return CR_OK; return CR_OK;
bool found = false;
for (df::job_list_link* link = &world->jobs.list; link != NULL; link = link->next)
{ {
if (link->item == NULL) continue; CoreSuspender suspend;
if (link->item->job_type == df::enums::job_type::UpdateStockpileRecords) tailor_instance->process(out);
{
found = true;
break;
}
}
if (found)
{
do_scan(out);
} }
return CR_OK; return CR_OK;
} }
static command_result tailor_cmd(color_ostream &out, vector <string> & parameters) { static command_result tailor_cmd(color_ostream& out, vector <string>& parameters) {
bool desired = enabled; bool desired = enabled;
if (parameters.size() == 1) if (parameters.size() == 1 && parameters[0] == "enable" || parameters[0] == "on" || parameters[0] == "1")
{ {
if (parameters[0] == "enable" || parameters[0] == "on" || parameters[0] == "1") desired = true;
{ }
desired = true; else if (parameters.size() == 1 && parameters[0] == "disable" || parameters[0] == "off" || parameters[0] == "0")
} {
else if (parameters[0] == "disable" || parameters[0] == "off" || parameters[0] == "0") desired = false;
}
else if (parameters.size() == 1 && parameters[0] == "usage" || parameters[0] == "help" || parameters[0] == "?")
{
out.print("%s: %s\nUsage:\n%s", plugin_name, tagline, usage);
return CR_OK;
}
else if (parameters.size() == 1 && parameters[0] == "test")
{
if (tailor_instance)
{ {
desired = false; tailor_instance->do_scan(out);
return CR_OK;
} }
else if (parameters[0] == "usage" || parameters[0] == "help" || parameters[0] == "?") else
{ {
out.print("%s: %s\nUsage:\n%s", plugin_name, tagline, usage); out.print("%s: not instantiated\n", plugin_name);
return CR_OK; return CR_FAILURE;
} }
else if (parameters[0] == "test") }
else if (parameters.size() > 1 && parameters[0] == "materials")
{
if (tailor_instance)
{ {
do_scan(out); return tailor_instance->set_materials(out, parameters);
return CR_OK;
} }
else if (parameters[0] != "status") else
{ {
return CR_WRONG_USAGE; out.print("%s: not instantiated\n", plugin_name);
return CR_FAILURE;
} }
} }
else else if (parameters.size() == 1 && parameters[0] != "status")
{
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
}
out.print("Tailor is %s %s.\n", (desired == enabled) ? "currently" : "now", desired ? "enabled" : "disabled");
if (tailor_instance)
{
out.print("Material list is: %s\n", tailor_instance->get_material_list().c_str());
}
else
{
out.print("%s: not instantiated\n", plugin_name);
}
out.print("Tailor is %s %s.\n", (desired == enabled)? "currently": "now", desired? "enabled": "disabled");
enabled = desired; enabled = desired;
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event event)
{ {
return CR_OK; return CR_OK;
} }
@ -495,8 +641,10 @@ DFhackCExport command_result plugin_enable(color_ostream& out, bool enable)
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init(color_ostream& out, std::vector <PluginCommand>& commands)
{ {
tailor_instance = std::move(dts::make_unique<Tailor>());
if (AUTOENABLE) { if (AUTOENABLE) {
enabled = true; enabled = true;
} }
@ -505,6 +653,9 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector <Plugin
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_shutdown(color_ostream &out) { DFhackCExport command_result plugin_shutdown(color_ostream& out)
{
tailor_instance.release();
return plugin_enable(out, false); return plugin_enable(out, false);
} }