Merge remote-tracking branch 'ab9rf/tailor-v2' into develop

Conflicts: docs/changelog.txt
develop
lethosor 2021-09-01 23:34:26 -04:00
commit 0d14a2ccef
No known key found for this signature in database
GPG Key ID: 76A269552F4F58C1
2 changed files with 466 additions and 314 deletions

@ -48,6 +48,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- `orders`: new ``sort`` command. sorts orders according to their repeat frequency. this prevents daily orders from blocking other orders for simlar items from ever getting completed.
- `tiletypes-here`, `tiletypes-here-point`: add ``--cursor`` and ``--quiet`` options to support non-interactive use cases
- `quickfort`: Dreamfort blueprint set improvements: extensive revision based on playtesting and feedback. includes updated ``onMapLoad_dreamfort.init`` settings file, enhanced automation orders, and premade profession definitions. see full changelog at https://github.com/DFHack/dfhack/pull/1921 and https://github.com/DFHack/dfhack/pull/1925
- `tailor`: allow user to specify which materials to be used, and in what order
## API
- The ``Items`` module ``moveTo*`` and ``remove`` functions now handle projectiles

@ -41,44 +41,92 @@ DFHACK_PLUGIN_IS_ENABLED(enabled);
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(ui);
const char *tagline = "Allow the bookkeeper to queue jobs to keep dwarfs in adequate clothing.";
const char *usage = (
const char* tagline = "Allow the bookkeeper to queue jobs to keep dwarfs in adequate clothing.";
const char* usage = (
" tailor enable\n"
" Enable the plugin.\n"
" tailor disable\n"
" Disable the plugin.\n"
" tailor 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"
"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"
"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"
"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 = {
{ 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}
};
const MatType
M_SILK = MatType("silk", df::job_material_category::mask_silk, df::armor_general_flags::SOFT),
M_CLOTH = MatType("cloth", df::job_material_category::mask_cloth, df::armor_general_flags::SOFT),
M_YARN = MatType("yarn", df::job_material_category::mask_yarn, df::armor_general_flags::SOFT),
M_LEATHER = MatType("leather", df::job_material_category::mask_leather, df::armor_general_flags::LEATHER);
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> needed; // 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
df::item_flags bad_flags;
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
std::map<MatType, int> supply;
available.empty();
needed.empty();
queued.empty();
orders.empty();
color_ostream* out;
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)
continue;
if (i->flags.bits.owned)
continue;
if (i->getWear() >= 1)
continue;
df::item_type t = i->getType();
int size = world->raws.creatures.all[i->getMakerRace()]->adultsize;
available[make_pair(t, size)] += 1;
available.clear();
needed.clear();
queued.clear();
sizes.clear();
orders.clear();
supply.clear();
}
// scan for clothing raw materials
for (auto i : world->items.other[df::items_other_id::CLOTH])
void scan_clothing()
{
if (i->flags.whole & bad_flags.whole)
continue;
if (!i->hasImprovements()) // only count dyed
continue;
MaterialInfo mat(i);
int ss = i->getStackSize();
if (mat.material)
for (auto i : world->items.other[df::items_other_id::ANY_GENERIC37]) // GENERIC37 is "clothing"
{
if (mat.material->flags.is_set(df::material_flags::SILK))
silk += ss;
else if (mat.material->flags.is_set(df::material_flags::THREAD_PLANT))
cloth += ss;
else if (mat.material->flags.is_set(df::material_flags::YARN))
yarn += ss;
}
}
if (i->flags.whole & bad_flags.whole)
continue;
if (i->flags.bits.owned)
continue;
if (i->getWear() >= 1)
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])
{
if (i->flags.whole & bad_flags.whole)
continue;
leather += i->getStackSize();
available[make_pair(t, size)] += 1;
}
}
out.print("available: silk %d yarn %d cloth %d leather %d\n", silk, yarn, cloth, leather);
// scan for units who need replacement clothing
for (auto u : world->units.active)
void scan_materials()
{
if (!Units::isOwnCiv(u) ||
!Units::isOwnGroup(u) ||
!Units::isActive(u) ||
Units::isBaby(u))
continue; // skip units we don't control
set <df::item_type> wearing;
wearing.empty();
for (auto i : world->items.other[df::items_other_id::CLOTH])
{
if (i->flags.whole & bad_flags.whole)
continue;
if (!i->hasImprovements()) // only count dyed
continue;
MaterialInfo mat(i);
int ss = i->getStackSize();
deque<df::item*> worn;
worn.empty();
if (mat.material)
{
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;
if (inv->item->getWear() > 0)
worn.push_back(inv->item);
else
wearing.insert(inv->item->getType());
supply[M_LEATHER] += i->getStackSize();
}
int size = world->raws.creatures.all[u->race]->adultsize;
sizes[size] = u->race;
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]);
}
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)
needed[make_pair(ty, size)] += 1;
}
if (!Units::isOwnCiv(u) ||
!Units::isOwnGroup(u) ||
!Units::isActive(u) ||
Units::isBaby(u))
continue; // skip units we don't control
for (auto w : worn)
{
auto ty = w->getType();
auto oo = itemTypeMap.find(ty);
if (oo == itemTypeMap.end())
continue;
df::job_type o = oo->second;
set <df::item_type> wearing;
wearing.clear();
int size = world->raws.creatures.all[w->getMakerRace()]->adultsize;
std::string description;
w->getItemDescription(&description, 0);
deque<df::item*> worn;
worn.clear();
if (available[make_pair(ty, size)] > 0)
for (auto inv : u->inventory)
{
if (w->flags.bits.owned)
{
bool confiscated = Items::setOwner(w, NULL);
out.print(
"%s %s from %s.\n",
(confiscated ? "Confiscated" : "Could not confiscate"),
description.c_str(),
Translation::TranslateName(&u->name, false).c_str()
);
}
if (inv->mode != df::unit_inventory_item::Worn)
continue;
if (inv->item->getWear() > 0)
worn.push_back(inv->item);
else
wearing.insert(inv->item->getType());
}
if (wearing.count(ty) == 0)
available[make_pair(ty, size)] -= 1;
int size = world->raws.creatures.all[u->race]->adultsize;
sizes[size] = u->race;
if (w->getWear() > 1)
w->flags.bits.dump = true;
for (auto ty : set<df::item_type>{ df::item_type::ARMOR, df::item_type::PANTS, df::item_type::SHOES })
{
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",
// description.c_str(),
// Translation::TranslateName(&u->name, false).c_str()
// );
orders[make_tuple(o, w->getSubtype(), size)] += 1;
auto ty = w->getType();
auto o = itemTypeMap.at(ty);
int size = world->raws.creatures.all[w->getMakerRace()]->adultsize;
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];
for (auto a : needed)
void create_orders()
{
df::item_type ty = a.first.first;
int size = a.first.second;
int count = a.second;
int sub = 0;
vector<int16_t> v;
switch (ty) {
case df::item_type::ARMOR: v = entity->resources.armor_type; break;
case df::item_type::GLOVES: v = entity->resources.gloves_type; break;
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;
}
auto entity = world->entities.all[ui->civ_id];
for (auto& a : needed)
{
df::item_type ty = a.first.first;
int size = a.first.second;
int count = a.second;
int sub = 0;
vector<int16_t> v;
for (auto vv : v) {
bool isClothing = false;
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;
case df::item_type::ARMOR: v = entity->resources.armor_type; break;
case df::item_type::GLOVES: v = entity->resources.gloves_type; break;
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;
}
if (isClothing)
{
sub = vv;
break;
for (auto vv : v) {
bool isClothing = false;
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
for (auto o : world->manager_orders)
void scan_existing_orders()
{
auto f = jobTypeMap.find(o->job_type);
if (f == jobTypeMap.end())
continue;
for (auto o : world->manager_orders)
{
auto f = jobTypeMap.find(o->job_type);
if (f == jobTypeMap.end())
continue;
auto sub = o->item_subtype;
int race = o->hist_figure_id;
if (race == -1)
continue; // -1 means that the race of the worker will determine the size made; we must ignore these jobs
auto sub = o->item_subtype;
int race = o->hist_figure_id;
if (race == -1)
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;
int sub;
int size;
auto entity = world->entities.all[ui->civ_id];
tie(ty, sub, size) = o.first;
int count = o.second;
if (count > 0)
for (auto& o : orders)
{
vector<int16_t> v;
BitArray<df::armor_general_flags>* fl;
string name_s, name_p;
df::job_type ty;
int sub;
int size;
switch (ty) {
case df::job_type::MakeArmor:
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;
}
tie(ty, sub, size) = o.first;
int count = o.second;
bool can_make = false;
for (auto vv : v)
if (count > 0)
{
if (vv == sub)
{
can_make = true;
vector<int16_t> v;
BitArray<df::armor_general_flags>* fl;
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;
}
}
if (!can_make)
{
out.print("Cannot make %s, skipped\n", name_p.c_str());
continue; // this civilization does not know how to make this item, so sorry
}
bool can_make = std::find(v.begin(), v.end(), sub) != v.end();
switch (ty) {
case df::item_type::ARMOR: break;
case df::item_type::GLOVES: break;
case df::item_type::HELM: break;
case df::item_type::PANTS: break;
case df::item_type::SHOES: break;
default: break;
if (!can_make)
{
out->print("tailor: civilization cannot make %s, skipped\n", name_p.c_str());
continue;
}
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)) {
mat.whole = df::job_material_category::mask_silk;
silk -= count;
}
else if (cloth > count + 10 && fl->is_set(df::armor_general_flags::SOFT)) {
mat.whole = df::job_material_category::mask_cloth;
cloth -= count;
reset();
// scan for useable clothing
scan_clothing();
// 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)) {
mat.whole = df::job_material_category::mask_yarn;
yarn -= count;
else {
newmat.push_back(*mm);
}
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();
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 = 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()
);
if (found)
{
do_scan(out);
}
}
}
};
static std::unique_ptr<Tailor> tailor_instance;
#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;
if (!Maps::IsValid())
@ -427,64 +561,76 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
if (world->frame_counter % DELTA_TICKS != 0)
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;
if (link->item->job_type == df::enums::job_type::UpdateStockpileRecords)
{
found = true;
break;
}
}
if (found)
{
do_scan(out);
CoreSuspender suspend;
tailor_instance->process(out);
}
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;
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;
}
else if (parameters[0] == "disable" || parameters[0] == "off" || parameters[0] == "0")
desired = true;
}
else if (parameters.size() == 1 && 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);
return CR_OK;
out.print("%s: not instantiated\n", plugin_name);
return CR_FAILURE;
}
else if (parameters[0] == "test")
}
else if (parameters.size() > 1 && parameters[0] == "materials")
{
if (tailor_instance)
{
do_scan(out);
return CR_OK;
return tailor_instance->set_materials(out, parameters);
}
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;
}
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;
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;
}
@ -495,8 +641,10 @@ DFhackCExport command_result plugin_enable(color_ostream& out, bool enable)
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) {
enabled = true;
}
@ -505,6 +653,9 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector <Plugin
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);
}