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,61 +1,85 @@
/* /*
* 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);
}
static const int32_t CYCLE_TICKS = 1200; // one day
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
const std::map<df::job_type, df::item_type> jobTypeMap = { // ah, if only STL had a bimap
static const std::map<df::job_type, df::item_type> jobTypeMap = {
{ df::job_type::MakeArmor, df::item_type::ARMOR }, { df::job_type::MakeArmor, df::item_type::ARMOR },
{ df::job_type::MakePants, df::item_type::PANTS }, { df::job_type::MakePants, df::item_type::PANTS },
{ df::job_type::MakeHelm, df::item_type::HELM }, { df::job_type::MakeHelm, df::item_type::HELM },
@ -63,7 +87,7 @@ private:
{ df::job_type::MakeShoes, df::item_type::SHOES } { df::job_type::MakeShoes, df::item_type::SHOES }
}; };
const std::map<df::item_type, df::job_type> itemTypeMap = { static const std::map<df::item_type, df::job_type> itemTypeMap = {
{ df::item_type::ARMOR, df::job_type::MakeArmor }, { df::item_type::ARMOR, df::job_type::MakeArmor },
{ df::item_type::PANTS, df::job_type::MakePants }, { df::item_type::PANTS, df::job_type::MakePants },
{ df::item_type::HELM, df::job_type::MakeHelm }, { df::item_type::HELM, df::job_type::MakeHelm },
@ -71,31 +95,18 @@ private:
{ df::item_type::SHOES, df::job_type::MakeShoes } { 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 { class MatType {
public: public:
std::string name; const std::string name;
df::job_material_category job_material; const df::job_material_category job_material;
df::armor_general_flags armor_flag; const df::armor_general_flags armor_flag;
bool operator==(const MatType& m) const bool operator==(const MatType& m) const {
{
return name == m.name; return name == m.name;
} }
// operator< is required to use this as a std::map key // operator< is required to use this as a std::map key
bool operator<(const MatType& m) const bool operator<(const MatType& m) const {
{
return name < m.name; return name < m.name;
} }
@ -103,34 +114,48 @@ private:
: name(n), job_material(jm), armor_flag(af) {}; : name(n), job_material(jm), armor_flag(af) {};
MatType(const char* n, df::job_material_category jm, df::armor_general_flags 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) {}; : name(std::string(n)), job_material(jm), armor_flag(af) {};
}; };
const MatType static const MatType
M_SILK = MatType("silk", df::job_material_category::mask_silk, df::armor_general_flags::SOFT), 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_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_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); 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 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();
// scan for clothing raw materials static command_result do_command(color_ostream &out, vector<string> &parameters);
static int do_cycle(color_ostream &out);
scan_materials(); DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
DEBUG(config,out).print("initializing %s\n", plugin_name);
// scan for units who need replacement clothing tailor_instance = dts::make_unique<Tailor>();
scan_replacements(); // provide a configuration interface for the plugin
commands.push_back(PluginCommand(
plugin_name,
"Automatically keep your dwarves in fresh clothing.",
do_command));
// create new orders return CR_OK;
}
create_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;
}
// scan existing orders and subtract 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(); DFhackCExport command_result plugin_shutdown (color_ostream &out) {
DEBUG(config,out).print("shutting down %s\n", plugin_name);
// place orders tailor_instance.release();
place_orders(); return CR_OK;
} }
public: static void set_material_order() {
command_result set_materials(color_ostream& out, std::vector<std::string>& parameters) material_order.clear();
{ for (int i = 0; i < all_materials.size(); ++i) {
std::list<MatType> newmat; if (i == get_config_val(config, CONFIG_SILK_IDX))
newmat.clear(); material_order.push_back(M_SILK);
else if (i == get_config_val(config, CONFIG_CLOTH_IDX))
for (auto m = parameters.begin() + 1; m != parameters.end(); m++) material_order.push_back(M_CLOTH);
{ else if (i == get_config_val(config, CONFIG_YARN_IDX))
auto nameMatch = [m](MatType& m1) { return *m == m1.name; }; material_order.push_back(M_YARN);
auto mm = std::find_if(all_materials.begin(), all_materials.end(), nameMatch); else if (i == get_config_val(config, CONFIG_LEATHER_IDX))
if (mm == all_materials.end()) material_order.push_back(M_LEATHER);
{
WARN(config,out).print("tailor: material %s not recognized\n", m->c_str());
return CR_WRONG_USAGE;
} }
else { if (!material_order.size())
newmat.push_back(*mm); std::copy(all_materials.begin(), all_materials.end(), std::back_inserter(material_order));
} }
DFhackCExport command_result plugin_load_data (color_ostream &out) {
cycle_timestamp = 0;
config = World::GetPersistentData(CONFIG_KEY);
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);
} }
material_order = newmat; is_enabled = get_config_bool(config, CONFIG_IS_ENABLED);
INFO(config,out).print("tailor: material list set to %s\n", get_material_list().c_str()); DEBUG(config,out).print("loading persisted enabled state: %s\n",
is_enabled ? "true" : "false");
set_material_order();
return CR_OK; return CR_OK;
} }
public: DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
std::string get_material_list() if (event == DFHack::SC_WORLD_UNLOADED) {
{ if (is_enabled) {
std::string s; DEBUG(config,out).print("world unloaded; disabling %s\n",
for (const auto& m : material_order) plugin_name);
{ is_enabled = false;
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;
} }
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,
#define DELTA_TICKS 50 Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA,
Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) {
DFhackCExport command_result plugin_onupdate(color_ostream& out) DEBUG(config).print("calling tailor lua function: '%s'\n", fn_name);
{
if (!enabled || !tailor_instance)
return CR_OK;
if (!Maps::IsValid()) CoreSuspender guard;
return CR_OK;
if (DFHack::World::ReadPauseState()) auto L = Lua::Core::State;
return CR_OK; Lua::StackUnwinder top(L);
if (world->frame_counter % DELTA_TICKS != 0) if (!out)
return CR_OK; out = &Core::getInstance().getConsole();
{ return Lua::CallLuaModuleFunction(*out, L, "plugins.tailor", fn_name,
CoreSuspender suspend; nargs, nres,
tailor_instance->process(out); std::forward<Lua::LuaLambda&&>(args_lambda),
std::forward<Lua::LuaLambda&&>(res_lambda));
} }
return CR_OK; static command_result do_command(color_ostream &out, vector<string> &parameters) {
} CoreSuspender suspend;
static command_result tailor_cmd(color_ostream& out, std::vector <std::string>& parameters) { if (!Core::getInstance().isWorldLoaded()) {
bool desired = enabled; out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
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; return CR_FAILURE;
} }
}
else if (parameters.size() > 1 && parameters[0] == "materials") bool show_help = false;
{ if (!call_tailor_lua(&out, "parse_commandline", parameters.size(), 1,
if (tailor_instance) [&](lua_State *L) {
{ for (const string &param : parameters)
return tailor_instance->set_materials(out, parameters); Lua::Push(L, param);
} },
else [&](lua_State *L) {
{ show_help = !lua_toboolean(L, -1);
out.print("%s: not instantiated\n", plugin_name); })) {
return CR_FAILURE; 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"); return show_help ? CR_WRONG_USAGE : CR_OK;
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);
} }
enabled = desired; /////////////////////////////////////////////////////
// cycle logic
//
return CR_OK; static int do_cycle(color_ostream &out) {
} // mark that we have recently run
cycle_timestamp = world->frame_counter;
DEBUG(cycle,out).print("running %s cycle\n", plugin_name);
DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event event) tailor_instance->reset();
{ tailor_instance->scan_clothing();
return CR_OK; tailor_instance->scan_materials();
tailor_instance->scan_replacements();
tailor_instance->create_orders();
tailor_instance->scan_existing_orders();
return tailor_instance->place_orders();
} }
DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) /////////////////////////////////////////////////////
{ // Lua API
enabled = enable; //
return CR_OK;
static void tailor_doCycle(color_ostream &out) {
DEBUG(config,out).print("entering tailor_doCycle\n");
out.print("ordered %d items of clothing\n", do_cycle(out));
} }
DFhackCExport command_result plugin_init(color_ostream& out, std::vector <PluginCommand>& commands) // remember, these are ONE-based indices from Lua
{ static void tailor_setMaterialPreferences(color_ostream &out, int32_t silkIdx,
tailor_instance = std::move(dts::make_unique<Tailor>()); int32_t clothIdx, int32_t yarnIdx, int32_t leatherIdx) {
DEBUG(config,out).print("entering tailor_setMaterialPreferences\n");
// it doesn't really matter if these are invalid. set_material_order will do
// the right thing.
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
};