update tailor, persist state, use best practices

develop
Myk Taylor 2023-02-06 18:38:16 -08:00
parent 5113823d8c
commit 5c84d18001
No known key found for this signature in database
GPG Key ID: 8A39CA0FA0C16E78
4 changed files with 350 additions and 263 deletions

@ -5,16 +5,15 @@ tailor
:summary: Automatically keep your dwarves in fresh clothing. :summary: Automatically keep your dwarves in fresh clothing.
:tags: fort auto workorders :tags: fort auto workorders
Whenever the bookkeeper updates stockpile records, this plugin will scan the Once a day, this plugin will scan the clothing situation in the fort. If there
fort. If there are fresh cloths available, dwarves who are wearing tattered are fresh cloths available, dwarves who are wearing tattered clothing will have
clothing will have their rags confiscated (in the same manner as the their rags confiscated (in the same manner as the `cleanowned` tool) so that
`cleanowned` tool) so that they'll reequip with replacement clothes. they'll reequip with replacement clothes.
If there are not enough clothes available, manager orders will be generated If there are not enough clothes available, manager orders will be generated to
to manufacture some more. ``tailor`` will intelligently create orders using manufacture some more. ``tailor`` will intelligently create orders using raw
raw materials that you have on hand in the fort. For example, if you have materials that you have on hand in the fort. For example, if you have lots of
lots of silk, but no cloth, then ``tailor`` will order only silk clothing to silk, but no cloth, then ``tailor`` will order only silk clothing to be made.
be made.
Usage Usage
----- -----
@ -22,7 +21,8 @@ Usage
:: ::
enable tailor enable tailor
tailor status tailor [status]
tailor now
tailor materials <material> [<material> ...] tailor materials <material> [<material> ...]
By default, ``tailor`` will prefer using materials in this order:: By default, ``tailor`` will prefer using materials in this order::
@ -32,12 +32,16 @@ By default, ``tailor`` will prefer using materials in this order::
but you can use the ``tailor materials`` command to restrict which materials but you can use the ``tailor materials`` command to restrict which materials
are used, and in what order. are used, and in what order.
Example Examples
------- --------
``enable tailor`` ``enable tailor``
Start replacing tattered clothes with default settings. Start replacing tattered clothes with default settings.
``tailor now``
Run a scan and order cycle right now, regardless of whether the plugin is
enabled.
``tailor materials silk cloth yarn`` ``tailor materials silk cloth yarn``
Restrict the materials used for automatically manufacturing clothing to Restrict the materials used for automatically manufacturing clothing to
silk, cloth, and yarn, preferred in that order. This saves leather for silk, cloth, and yarn, preferred in that order. This saves leather for

@ -159,7 +159,7 @@ dfhack_plugin(showmood showmood.cpp)
#add_subdirectory(stockpiles) #add_subdirectory(stockpiles)
#dfhack_plugin(stocks stocks.cpp) #dfhack_plugin(stocks stocks.cpp)
#dfhack_plugin(strangemood strangemood.cpp) #dfhack_plugin(strangemood strangemood.cpp)
dfhack_plugin(tailor tailor.cpp) dfhack_plugin(tailor tailor.cpp LINK_LIBRARIES lua)
dfhack_plugin(tiletypes tiletypes.cpp Brushes.h LINK_LIBRARIES lua) dfhack_plugin(tiletypes tiletypes.cpp Brushes.h LINK_LIBRARIES lua)
#dfhack_plugin(title-folder title-folder.cpp) #dfhack_plugin(title-folder title-folder.cpp)
#dfhack_plugin(title-version title-version.cpp) #dfhack_plugin(title-version title-version.cpp)

@ -0,0 +1,56 @@
local _ENV = mkmodule('plugins.tailor')
local argparse = require('argparse')
local utils = require('utils')
local function process_args(opts, args)
if args[1] == 'help' then
opts.help = true
return
end
return argparse.processArgsGetopt(args, {
{'h', 'help', handler=function() opts.help = true end},
})
end
function status()
print(('tailor is %s'):format(enabled and "enabled" or "disabled"))
print('materials preference order:')
for _,name in ipairs(tailor_getMaterialPreferences()) do
print((' %s'):format(name))
end
end
function setMaterials(names)
local idxs = utils.invert(names)
tailor_setMaterialPreferences(
idxs.silk or -1,
idxs.cloth or -1,
idxs.yarn or -1,
idxs.leather or -1)
end
function parse_commandline(...)
local args, opts = {...}, {}
local positionals = process_args(opts, args)
if opts.help then
return false
end
local command = table.remove(positionals, 1)
if not command or command == 'status' then
status()
elseif command == 'now' then
tailor_doCycle()
elseif command == 'materials' then
setMaterials(positionals)
else
return false
end
return true
end
return _ENV

@ -1,136 +1,161 @@
/* /*
* Tailor plugin. Automatically manages keeping your dorfs clothed. * Tailor plugin. Automatically manages keeping your dorfs clothed.
* For best effect, place "tailor enable" in your dfhack.init configuration,
* or set AUTOENABLE to true.
*/ */
#include "Core.h" #include <string>
#include "DataDefs.h" #include <unordered_map>
#include "Debug.h" #include <vector>
#include "PluginManager.h"
#include "df/creature_raw.h" #include "df/creature_raw.h"
#include "df/global_objects.h"
#include "df/historical_entity.h" #include "df/historical_entity.h"
#include "df/item.h"
#include "df/item_flags.h"
#include "df/itemdef_armorst.h" #include "df/itemdef_armorst.h"
#include "df/itemdef_glovesst.h" #include "df/itemdef_glovesst.h"
#include "df/itemdef_helmst.h" #include "df/itemdef_helmst.h"
#include "df/itemdef_pantsst.h" #include "df/itemdef_pantsst.h"
#include "df/itemdef_shoesst.h" #include "df/itemdef_shoesst.h"
#include "df/items_other_id.h" #include "df/items_other_id.h"
#include "df/job.h"
#include "df/job_type.h"
#include "df/manager_order.h" #include "df/manager_order.h"
#include "df/plotinfost.h" #include "df/plotinfost.h"
#include "df/world.h" #include "df/world.h"
#include "modules/Maps.h" #include "Core.h"
#include "modules/Units.h" #include "Debug.h"
#include "LuaTools.h"
#include "PluginManager.h"
#include "modules/Materials.h"
#include "modules/Persistence.h"
#include "modules/Translation.h" #include "modules/Translation.h"
#include "modules/Units.h"
#include "modules/World.h" #include "modules/World.h"
using namespace DFHack; using std::string;
using std::vector;
using df::global::world; using namespace DFHack;
using df::global::plotinfo;
DFHACK_PLUGIN("tailor"); DFHACK_PLUGIN("tailor");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
#define AUTOENABLE false
DFHACK_PLUGIN_IS_ENABLED(enabled);
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(plotinfo); REQUIRE_GLOBAL(plotinfo);
REQUIRE_GLOBAL(standing_orders_use_dyed_cloth); REQUIRE_GLOBAL(standing_orders_use_dyed_cloth);
REQUIRE_GLOBAL(world);
namespace DFHack { namespace DFHack {
DBG_DECLARE(tailor, cycle, DebugCategory::LINFO); DBG_DECLARE(tailor, cycle, DebugCategory::LINFO);
DBG_DECLARE(tailor, config, DebugCategory::LINFO); DBG_DECLARE(tailor, config, DebugCategory::LINFO);
} }
class Tailor { static const string CONFIG_KEY = string(plugin_name) + "/config";
// ARMOR, SHOES, HELM, GLOVES, PANTS static PersistentDataItem config;
// ah, if only STL had a bimap enum ConfigValues {
CONFIG_IS_ENABLED = 0,
CONFIG_SILK_IDX = 1,
CONFIG_CLOTH_IDX = 2,
CONFIG_YARN_IDX = 3,
CONFIG_LEATHER_IDX = 4,
};
private: static int get_config_val(PersistentDataItem &c, int index) {
if (!c.isValid())
return -1;
return c.ival(index);
}
static bool get_config_bool(PersistentDataItem &c, int index) {
return get_config_val(c, index) == 1;
}
static void set_config_val(PersistentDataItem &c, int index, int value) {
if (c.isValid())
c.ival(index) = value;
}
static void set_config_bool(PersistentDataItem &c, int index, bool value) {
set_config_val(c, index, value ? 1 : 0);
}
const std::map<df::job_type, df::item_type> jobTypeMap = { static const int32_t CYCLE_TICKS = 1200; // one day
{ df::job_type::MakeArmor, df::item_type::ARMOR }, static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
{ 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 std::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;
}
// operator< is required to use this as a std::map key // ah, if only STL had a bimap
bool operator<(const MatType& m) const static const std::map<df::job_type, df::item_type> jobTypeMap = {
{ { df::job_type::MakeArmor, df::item_type::ARMOR },
return name < m.name; { 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 }
};
MatType(std::string& n, df::job_material_category jm, df::armor_general_flags af) static const std::map<df::item_type, df::job_type> itemTypeMap = {
: name(n), job_material(jm), armor_flag(af) {}; { df::item_type::ARMOR, df::job_type::MakeArmor },
MatType(const char* n, df::job_material_category jm, df::armor_general_flags af) { df::item_type::PANTS, df::job_type::MakePants },
: name(std::string(n)), job_material(jm), armor_flag(af) {}; { df::item_type::HELM, df::job_type::MakeHelm },
{ df::item_type::GLOVES, df::job_type::MakeGloves },
{ df::item_type::SHOES, df::job_type::MakeShoes }
};
class MatType {
public:
const std::string name;
const df::job_material_category job_material;
const df::armor_general_flags armor_flag;
bool operator==(const MatType& m) const {
return name == m.name;
}
}; // operator< is required to use this as a std::map key
bool operator<(const MatType& m) const {
return name < m.name;
}
const MatType MatType(std::string& n, df::job_material_category jm, df::armor_general_flags af)
M_SILK = MatType("silk", df::job_material_category::mask_silk, df::armor_general_flags::SOFT), : name(n), job_material(jm), armor_flag(af) {};
M_CLOTH = MatType("cloth", df::job_material_category::mask_cloth, df::armor_general_flags::SOFT), MatType(const char* n, df::job_material_category jm, df::armor_general_flags af)
M_YARN = MatType("yarn", df::job_material_category::mask_yarn, df::armor_general_flags::SOFT), : name(std::string(n)), job_material(jm), armor_flag(af) {};
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 }; static 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);
static const std::list<MatType> all_materials = { M_SILK, M_CLOTH, M_YARN, M_LEATHER };
static std::list<MatType> material_order = all_materials;
static struct BadFlags {
uint32_t whole;
BadFlags() {
df::item_flags flags;
#define F(x) 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);
F(in_chest); F(removed); F(encased);
F(spider_web);
#undef F
whole = flags.whole;
}
} 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> 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> needed; // same
std::map<std::pair<df::item_type, int>, int> queued; // same std::map<std::pair<df::item_type, int>, int> queued; // same
std::map<int, int> sizes; // this maps body size to races 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::tuple<df::job_type, int, int>, int> orders; // key is item type, item subtype, size
std::map<MatType, int> supply; std::map<MatType, int> supply;
color_ostream* out;
std::list<MatType> material_order = { M_SILK, M_CLOTH, M_YARN, M_LEATHER };
std::map<MatType, int> reserves; std::map<MatType, int> reserves;
int default_reserve = 10; int default_reserve = 10;
public:
void reset() void reset()
{ {
available.clear(); available.clear();
@ -145,9 +170,7 @@ private:
{ {
for (auto i : world->items.other[df::items_other_id::ANY_GENERIC37]) // GENERIC37 is "clothing" for (auto i : world->items.other[df::items_other_id::ANY_GENERIC37]) // GENERIC37 is "clothing"
{ {
if (i->flags.whole & bad_flags.whole) if (i->flags.whole & badFlags.whole)
continue;
if (i->flags.bits.owned)
continue; continue;
if (i->getWear() >= 1) if (i->getWear() >= 1)
continue; continue;
@ -164,7 +187,7 @@ private:
for (auto i : world->items.other[df::items_other_id::CLOTH]) for (auto i : world->items.other[df::items_other_id::CLOTH])
{ {
if (i->flags.whole & bad_flags.whole) if (i->flags.whole & badFlags.whole)
continue; continue;
if (require_dyed && !i->hasImprovements()) if (require_dyed && !i->hasImprovements())
@ -197,7 +220,7 @@ private:
for (auto i : world->items.other[df::items_other_id::SKIN_TANNED]) for (auto i : world->items.other[df::items_other_id::SKIN_TANNED])
{ {
if (i->flags.whole & bad_flags.whole) if (i->flags.whole & badFlags.whole)
continue; continue;
supply[M_LEATHER] += i->getStackSize(); supply[M_LEATHER] += i->getStackSize();
} }
@ -369,8 +392,9 @@ private:
} }
void place_orders() int place_orders()
{ {
int ordered = 0;
auto entity = world->entities.all[plotinfo->civ_id]; auto entity = world->entities.all[plotinfo->civ_id];
for (auto& o : orders) for (auto& o : orders)
@ -477,6 +501,7 @@ private:
); );
count -= c; count -= c;
ordered += c;
} }
else else
{ {
@ -486,215 +511,217 @@ private:
} }
} }
} }
return ordered;
} }
};
public: static std::unique_ptr<Tailor> tailor_instance;
void do_scan(color_ostream& o)
{
out = &o;
reset();
// scan for useable clothing
scan_clothing(); static command_result do_command(color_ostream &out, vector<string> &parameters);
static int do_cycle(color_ostream &out);
// scan for clothing raw materials DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
DEBUG(config,out).print("initializing %s\n", plugin_name);
scan_materials(); tailor_instance = dts::make_unique<Tailor>();
// scan for units who need replacement clothing // provide a configuration interface for the plugin
commands.push_back(PluginCommand(
plugin_name,
"Automatically keep your dwarves in fresh clothing.",
do_command));
scan_replacements(); return CR_OK;
}
// create new orders DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot enable %s without a loaded world.\n", plugin_name);
return CR_FAILURE;
}
create_orders(); if (enable != is_enabled) {
is_enabled = enable;
DEBUG(config,out).print("%s from the API; persisting\n",
is_enabled ? "enabled" : "disabled");
set_config_bool(config, CONFIG_IS_ENABLED, is_enabled);
if (enable)
do_cycle(out);
} else {
DEBUG(config,out).print("%s from the API, but already %s; no action\n",
is_enabled ? "enabled" : "disabled",
is_enabled ? "enabled" : "disabled");
}
return CR_OK;
}
// scan existing orders and subtract DFhackCExport command_result plugin_shutdown (color_ostream &out) {
DEBUG(config,out).print("shutting down %s\n", plugin_name);
scan_existing_orders(); tailor_instance.release();
// place orders return CR_OK;
}
place_orders(); static void set_material_order() {
material_order.clear();
for (int i = 0; i < all_materials.size(); ++i) {
if (i == get_config_val(config, CONFIG_SILK_IDX))
material_order.push_back(M_SILK);
else if (i == get_config_val(config, CONFIG_CLOTH_IDX))
material_order.push_back(M_CLOTH);
else if (i == get_config_val(config, CONFIG_YARN_IDX))
material_order.push_back(M_YARN);
else if (i == get_config_val(config, CONFIG_LEATHER_IDX))
material_order.push_back(M_LEATHER);
} }
if (!material_order.size())
std::copy(all_materials.begin(), all_materials.end(), std::back_inserter(material_order));
}
public: DFhackCExport command_result plugin_load_data (color_ostream &out) {
command_result set_materials(color_ostream& out, std::vector<std::string>& parameters) cycle_timestamp = 0;
{ config = World::GetPersistentData(CONFIG_KEY);
std::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())
{
WARN(config,out).print("tailor: material %s not recognized\n", m->c_str());
return CR_WRONG_USAGE;
}
else {
newmat.push_back(*mm);
}
}
material_order = newmat;
INFO(config,out).print("tailor: material list set to %s\n", get_material_list().c_str());
return CR_OK; if (!config.isValid()) {
DEBUG(config,out).print("no config found in this save; initializing\n");
config = World::AddPersistentData(CONFIG_KEY);
set_config_bool(config, CONFIG_IS_ENABLED, is_enabled);
} }
public: is_enabled = get_config_bool(config, CONFIG_IS_ENABLED);
std::string get_material_list() DEBUG(config,out).print("loading persisted enabled state: %s\n",
{ is_enabled ? "true" : "false");
std::string s; set_material_order();
for (const auto& m : material_order)
{
if (!s.empty()) s += ", ";
s += m.name;
}
return s;
}
public: return CR_OK;
void process(color_ostream& out) }
{
bool found = false;
for (df::job_list_link* link = &world->jobs.list; link != NULL; link = link->next) DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
{ if (event == DFHack::SC_WORLD_UNLOADED) {
if (link->item == NULL) continue; if (is_enabled) {
if (link->item->job_type == df::enums::job_type::UpdateStockpileRecords) DEBUG(config,out).print("world unloaded; disabling %s\n",
{ plugin_name);
found = true; is_enabled = false;
break;
}
} }
}
return CR_OK;
}
if (found) DFhackCExport command_result plugin_onupdate(color_ostream &out) {
{ if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS) {
do_scan(out); int ordered = do_cycle(out);
} if (0 < ordered)
out.print("tailor: ordered %d items of clothing\n", ordered);
} }
}; return CR_OK;
}
static std::unique_ptr<Tailor> tailor_instance; static bool call_tailor_lua(color_ostream *out, const char *fn_name,
int nargs = 0, int nres = 0,
Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA,
Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) {
DEBUG(config).print("calling tailor lua function: '%s'\n", fn_name);
#define DELTA_TICKS 50 CoreSuspender guard;
DFhackCExport command_result plugin_onupdate(color_ostream& out) auto L = Lua::Core::State;
{ Lua::StackUnwinder top(L);
if (!enabled || !tailor_instance)
return CR_OK;
if (!Maps::IsValid()) if (!out)
return CR_OK; out = &Core::getInstance().getConsole();
if (DFHack::World::ReadPauseState()) return Lua::CallLuaModuleFunction(*out, L, "plugins.tailor", fn_name,
return CR_OK; nargs, nres,
std::forward<Lua::LuaLambda&&>(args_lambda),
std::forward<Lua::LuaLambda&&>(res_lambda));
}
if (world->frame_counter % DELTA_TICKS != 0) static command_result do_command(color_ostream &out, vector<string> &parameters) {
return CR_OK; CoreSuspender suspend;
{ if (!Core::getInstance().isWorldLoaded()) {
CoreSuspender suspend; out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
tailor_instance->process(out); return CR_FAILURE;
} }
return CR_OK; bool show_help = false;
if (!call_tailor_lua(&out, "parse_commandline", parameters.size(), 1,
[&](lua_State *L) {
for (const string &param : parameters)
Lua::Push(L, param);
},
[&](lua_State *L) {
show_help = !lua_toboolean(L, -1);
})) {
return CR_FAILURE;
}
return show_help ? CR_WRONG_USAGE : CR_OK;
} }
static command_result tailor_cmd(color_ostream& out, std::vector <std::string>& parameters) { /////////////////////////////////////////////////////
bool desired = enabled; // cycle logic
if (parameters.size() == 1 && (parameters[0] == "enable" || parameters[0] == "on" || parameters[0] == "1")) //
{
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] == "?"))
{
return CR_WRONG_USAGE;
}
else if (parameters.size() == 1 && parameters[0] == "test")
{
if (tailor_instance)
{
tailor_instance->do_scan(out);
return CR_OK;
}
else
{
out.print("%s: not instantiated\n", plugin_name);
return CR_FAILURE;
}
}
else if (parameters.size() > 1 && parameters[0] == "materials")
{
if (tailor_instance)
{
return tailor_instance->set_materials(out, parameters);
}
else
{
out.print("%s: not instantiated\n", plugin_name);
return CR_FAILURE;
}
}
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"); static int do_cycle(color_ostream &out) {
if (tailor_instance) // mark that we have recently run
{ cycle_timestamp = world->frame_counter;
out.print("Material list is: %s\n", tailor_instance->get_material_list().c_str());
}
else
{
out.print("%s: not instantiated\n", plugin_name);
}
enabled = desired; DEBUG(cycle,out).print("running %s cycle\n", plugin_name);
return CR_OK; tailor_instance->reset();
tailor_instance->scan_clothing();
tailor_instance->scan_materials();
tailor_instance->scan_replacements();
tailor_instance->create_orders();
tailor_instance->scan_existing_orders();
return tailor_instance->place_orders();
} }
/////////////////////////////////////////////////////
// Lua API
//
DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event event) static void tailor_doCycle(color_ostream &out) {
{ DEBUG(config,out).print("entering tailor_doCycle\n");
return CR_OK; out.print("ordered %d items of clothing\n", do_cycle(out));
} }
DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) // remember, these are ONE-based indices from Lua
{ static void tailor_setMaterialPreferences(color_ostream &out, int32_t silkIdx,
enabled = enable; int32_t clothIdx, int32_t yarnIdx, int32_t leatherIdx) {
return CR_OK; DEBUG(config,out).print("entering tailor_setMaterialPreferences\n");
}
DFhackCExport command_result plugin_init(color_ostream& out, std::vector <PluginCommand>& commands) // it doesn't really matter if these are invalid. set_material_order will do
{ // the right thing.
tailor_instance = std::move(dts::make_unique<Tailor>()); set_config_val(config, CONFIG_SILK_IDX, silkIdx);
set_config_val(config, CONFIG_CLOTH_IDX, clothIdx);
set_config_val(config, CONFIG_YARN_IDX, yarnIdx);
set_config_val(config, CONFIG_LEATHER_IDX, leatherIdx);
if (AUTOENABLE) { set_material_order();
enabled = true; }
}
commands.push_back(PluginCommand( static int tailor_getMaterialPreferences(lua_State *L) {
plugin_name, color_ostream *out = Lua::GetOutput(L);
"Automatically keep your dwarves in fresh clothing.", if (!out)
tailor_cmd)); out = &Core::getInstance().getConsole();
return CR_OK; DEBUG(config,*out).print("entering tailor_getMaterialPreferences\n");
vector<string> names;
for (const auto& m : material_order)
names.emplace_back(m.name);
Lua::PushVector(L, names);
return 1;
} }
DFhackCExport command_result plugin_shutdown(color_ostream& out) DFHACK_PLUGIN_LUA_FUNCTIONS {
{ DFHACK_LUA_FUNCTION(tailor_doCycle),
tailor_instance.release(); DFHACK_LUA_FUNCTION(tailor_setMaterialPreferences),
DFHACK_LUA_END
};
return plugin_enable(out, false); DFHACK_PLUGIN_LUA_COMMANDS {
} DFHACK_LUA_COMMAND(tailor_getMaterialPreferences),
DFHACK_LUA_END
};