Merge branch 'develop' into fix-autoslab

develop
John Cosker 2023-02-08 22:39:46 -05:00 committed by GitHub
commit cf51054f86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 795 additions and 524 deletions

@ -189,8 +189,8 @@ if(NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl
endif()
# set up versioning.
set(DF_VERSION "50.05")
set(DFHACK_RELEASE "alpha3.1")
set(DF_VERSION "50.07")
set(DFHACK_RELEASE "alpha0")
set(DFHACK_PRERELEASE TRUE)
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 997 B

@ -1138,42 +1138,6 @@
"job" : "MakeWeapon",
"material" : "INORGANIC:SILVER"
},
{
"amount_left" : 1,
"amount_total" : 1,
"frequency" : "Daily",
"id" : 64,
"is_active" : false,
"is_validated" : false,
"item_conditions" :
[
{
"condition" : "AtLeast",
"item_type" : "BAR",
"material" : "INORGANIC:SILVER",
"value" : 20
},
{
"condition" : "AtLeast",
"item_type" : "BAR",
"material" : "COAL",
"value" : 100
},
{
"condition" : "AtMost",
"flags" :
[
"metal"
],
"item_subtype" : "ITEM_WEAPON_CROSSBOW",
"item_type" : "WEAPON",
"value" : 10
}
],
"item_subtype" : "ITEM_WEAPON_CROSSBOW",
"job" : "MakeWeapon",
"material" : "INORGANIC:SILVER"
},
{
"amount_left" : 1,
"amount_total" : 1,
@ -1656,12 +1620,6 @@
"item_type" : "WEAPON",
"material" : "INORGANIC:STEEL",
"value" : 10
},
{
"condition" : "LessThan",
"item_type" : "BAR",
"material" : "INORGANIC:SILVER",
"value" : 5
}
],
"item_subtype" : "ITEM_WEAPON_CROSSBOW",
@ -2357,12 +2315,6 @@
"material" : "INORGANIC:STEEL",
"value" : 30
},
{
"condition" : "LessThan",
"item_type" : "BAR",
"material" : "INORGANIC:SILVER",
"value" : 5
},
{
"condition" : "AtMost",
"flags" :
@ -3068,12 +3020,6 @@
"material" : "INORGANIC:STEEL",
"value" : 30
},
{
"condition" : "LessThan",
"item_type" : "BAR",
"material" : "INORGANIC:SILVER",
"value" : 5
},
{
"condition" : "AtMost",
"flags" :
@ -3856,12 +3802,6 @@
"material" : "INORGANIC:STEEL",
"value" : 30
},
{
"condition" : "LessThan",
"item_type" : "BAR",
"material" : "INORGANIC:SILVER",
"value" : 5
},
{
"condition" : "AtMost",
"flags" :
@ -4734,12 +4674,6 @@
"material" : "INORGANIC:STEEL",
"value" : 30
},
{
"condition" : "LessThan",
"item_type" : "BAR",
"material" : "INORGANIC:SILVER",
"value" : 5
},
{
"condition" : "AtMost",
"flags" :

@ -39,16 +39,22 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- ``Units::isFortControlled``: Account for agitated wildlife
- Fix right click sometimes closing both a DFHack window and a vanilla panel
- Fixed issue with scrollable lists having some data off-screen if they were scrolled before being made visible
- `channel-safely`: fixed bug resulting in marker mode never being set for any designation
-@ `automelt`: fixed bug related to lua stack smashing behavior in returned stockpile configs
-@ `autochop`: fixed bug related to lua stack smashing behavior in returned stockpile configs
## Misc Improvements
- `automelt`: is now more resistent to savegame corruption
- `hotkeys`: DFHack logo is now hidden on screens where it covers important information when in the default position (e.g. when choosing an embark site)
-@ `hotkeys`: DFHack logo is now hidden on screens where it covers important information when in the default position (e.g. when choosing an embark site)
- `misery`: now persists state with the fort
- `autodump`: reinstate ``autodump-destroy-item``, hotkey: Ctrl-K
- `autodump`: new hotkey for ``autodump-destroy-here``: Ctrl-H
- `dig`: new hotkeys for vein designation on z-level (Ctrl-V) and vein designation across z-levels (Ctrl-Shift-V)
- `clean`: new hotkey for `spotclean`: Ctrl-C
- `autobutcher`: changed defaults from 5 females / 1 male to 4 females / 2 males so a single unfortunate accident doesn't leave players without a mating pair
- `autobutcher`: now immediately loads races available at game start into the watchlist
-@ replaced DFHack logo used for the hover hotspot with a crisper image
- `orders`: recipe for silver crossbows removed from ``library/military`` as it is not a vanilla recipe, but is available in ``library/military_include_artifact_materials``
## Documentation
@ -57,13 +63,16 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Lua
- `overlay`: overlay widgets can now specify focus paths for the viewscreens they attach to so they only appear in specific contexts. see `overlay-dev-guide` for details.
- ``widgets.CycleHotkeyLabel``: Added ``key_back`` optional parameter to cycle backwards.
- ``widgets.HotkeyLabel``: Added ``setLabel`` method to allow easily updating the label text without mangling the keyboard shortcut.
- ``widgets.HotkeyLabel``: Added ``setOnActivate`` method to allow easily updating the ``on_activate`` callback.
- ``widgets.FilteredList``: Added ``case_sensitive`` optional paramter to determine if filtering is case sensitive.
## Removed
# 50.05-alpha3.1
## Fixes
- `seedwatch`: fix parameter parsing when setting targets
-@ `seedwatch`: fix parameter parsing when setting targets
# 50.05-alpha3

@ -4808,6 +4808,16 @@ It has the following attributes:
:on_activate: If specified, it is the callback that will be called whenever
the hotkey is pressed or the label is clicked.
The HotkeyLabel widget implements the following methods:
* ``hotkeylabel:setLabel(label)``
Updates the label without altering the hotkey text.
* ``hotkeylabel:setOnActivate(on_activate)``
Updates the on_activate callback.
CycleHotkeyLabel class
----------------------
@ -4958,6 +4968,7 @@ construction that allows filtering the list by subwords of its items.
In addition to passing through all attributes supported by List, it
supports:
:case_sensitive: If true, matching is case sensitive. Defaults to true.
:edit_pen: If specified, used instead of ``cursor_pen`` for the edit field.
:edit_below: If true, the edit field is placed below the list instead of above.
:edit_key: If specified, the edit field is disabled until this key is pressed.

@ -2,18 +2,35 @@ misery
======
.. dfhack-tool::
:summary: Increase the intensity of negative dwarven thoughts.
:tags: fort armok auto units
:summary: Increase the intensity of your citizens' negative thoughts.
:tags: fort gameplay units
When enabled, negative thoughts that your dwarves have will multiply by the
specified factor.
When enabled, negative thoughts that your citizens have will multiply by the
specified factor. This makes it more challenging to keep them happy.
Usage
-----
::
enable misery
misery [status]
misery <factor>
misery clear
The default misery factor is ``2``, meaning that your dwarves will become
miserable twice as fast.
Examples
--------
``enable misery``
Start multiplying negative thoughts.
``misery <factor>``
Change the multiplicative factor of bad thoughts. The default is ``2``.
Start multiplying bad thoughts for your citizens!
``misery 5``
Make dwarves become unhappy 5 times faster than normal -- this is quite
challenging to handle!
``misery clear``
Clear away negative thoughts added by ``misery``.
Clear away negative thoughts added by ``misery``. Note that this will not
clear negative thoughts that your dwarves accumulated "naturally".

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

@ -478,7 +478,7 @@ static void OpenPersistent(lua_State *state)
static int DFHACK_MATINFO_TOKEN = 0;
void Lua::Push(lua_State *state, MaterialInfo &info)
void Lua::Push(lua_State *state, const MaterialInfo &info)
{
if (!info.isValid())
{

@ -101,7 +101,7 @@ void DFHack::Lua::Push(lua_State *state, const Units::NoblePosition &pos)
lua_setfield(state, -2, "position");
}
void DFHack::Lua::Push(lua_State *state, df::coord pos)
void DFHack::Lua::Push(lua_State *state, const df::coord &pos)
{
lua_createtable(state, 0, 3);
lua_pushinteger(state, pos.x);
@ -112,7 +112,7 @@ void DFHack::Lua::Push(lua_State *state, df::coord pos)
lua_setfield(state, -2, "z");
}
void DFHack::Lua::Push(lua_State *state, df::coord2d pos)
void DFHack::Lua::Push(lua_State *state, const df::coord2d &pos)
{
lua_createtable(state, 0, 2);
lua_pushinteger(state, pos.x);
@ -191,7 +191,7 @@ void DFHack::Lua::PushInterfaceKeys(lua_State *L,
}
}
int DFHack::Lua::PushPosXYZ(lua_State *state, df::coord pos)
int DFHack::Lua::PushPosXYZ(lua_State *state, const df::coord &pos)
{
if (!pos.isValid())
{
@ -207,7 +207,7 @@ int DFHack::Lua::PushPosXYZ(lua_State *state, df::coord pos)
}
}
int DFHack::Lua::PushPosXY(lua_State *state, df::coord2d pos)
int DFHack::Lua::PushPosXY(lua_State *state, const df::coord2d &pos)
{
if (!pos.isValid())
{

@ -325,10 +325,10 @@ namespace DFHack {namespace Lua {
inline void Push(lua_State *state, const std::string &str) {
lua_pushlstring(state, str.data(), str.size());
}
DFHACK_EXPORT void Push(lua_State *state, df::coord obj);
DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj);
DFHACK_EXPORT void Push(lua_State *state, const df::coord &obj);
DFHACK_EXPORT void Push(lua_State *state, const df::coord2d &obj);
void Push(lua_State *state, const Units::NoblePosition &pos);
DFHACK_EXPORT void Push(lua_State *state, MaterialInfo &info);
DFHACK_EXPORT void Push(lua_State *state, const MaterialInfo &info);
DFHACK_EXPORT void Push(lua_State *state, const Screen::Pen &info);
template<class T> inline void Push(lua_State *state, T *ptr) {
PushDFObject(state, ptr);
@ -361,29 +361,34 @@ namespace DFHack {namespace Lua {
DFHACK_EXPORT void GetVector(lua_State *state, std::vector<std::string> &pvec);
DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos);
DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos);
template <typename T_Key, typename T_Value>
inline void TableInsert(lua_State *state, T_Key key, T_Value value)
{
Lua::Push(state, key);
Lua::Push(state, value);
lua_settable(state, -3);
}
DFHACK_EXPORT int PushPosXYZ(lua_State *state, const df::coord &pos);
DFHACK_EXPORT int PushPosXY(lua_State *state, const df::coord2d &pos);
template<typename T_Key, typename T_Value>
void Push(lua_State *L, const std::map<T_Key, T_Value> &pmap) {
lua_createtable(L, 0, pmap.size());
for (auto &entry : pmap)
TableInsert(L, entry.first, entry.second);
for (auto &entry : pmap) {
Lua::Push(L, entry.first);
Lua::Push(L, entry.second);
lua_settable(L, -3);
}
}
template<typename T_Key, typename T_Value>
void Push(lua_State *L, const std::unordered_map<T_Key, T_Value> &pmap) {
lua_createtable(L, 0, pmap.size());
for (auto &entry : pmap)
TableInsert(L, entry.first, entry.second);
for (auto &entry : pmap) {
Lua::Push(L, entry.first);
Lua::Push(L, entry.second);
lua_settable(L, -3);
}
}
template <typename T_Key, typename T_Value>
inline void TableInsert(lua_State *state, const T_Key &key, const T_Value &value) {
Lua::Push(state, key);
Lua::Push(state, value);
lua_settable(state, -3);
}
DFHACK_EXPORT void CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil = false, bool allow_color = true);

@ -1419,6 +1419,20 @@ HotkeyLabel.ATTRS{
}
function HotkeyLabel:init()
self:initializeLabel()
end
function HotkeyLabel:setOnActivate(on_activate)
self.on_activate = on_activate
self:initializeLabel()
end
function HotkeyLabel:setLabel(label)
self.label = label
self:initializeLabel()
end
function HotkeyLabel:initializeLabel()
self:setText{{key=self.key, key_sep=self.key_sep, text=self.label,
on_activate=self.on_activate}}
end
@ -1868,6 +1882,7 @@ end
FilteredList = defclass(FilteredList, Widget)
FilteredList.ATTRS {
case_sensitive = true,
edit_below = false,
edit_key = DEFAULT_NIL,
edit_ignore_keys = DEFAULT_NIL,
@ -2028,13 +2043,19 @@ function FilteredList:setFilter(filter, pos)
-- start matches at non-space or non-punctuation. this allows
-- punctuation itself to be matched if that is useful (e.g.
-- filenames or parameter names)
if key ~= '' and
not search_key:match('%f[^%p\x00]'..key) and
if key ~= '' then
if not self.case_sensitive then
search_key = string.lower(search_key)
key = string.lower(key)
end
if not search_key:match('%f[^%p\x00]'..key) and
not search_key:match('%f[^%s\x00]'..key) then
ok = false
break
end
end
end
if ok then
table.insert(choices, v)
cidx[#choices] = i

@ -1 +1 @@
Subproject commit 68f8bfa92b68ef9bc30da7f5a8ea5cf91f69ea16
Subproject commit eff493010d11358fc8243239dbf8d07024eedb0c

@ -130,7 +130,7 @@ dfhack_plugin(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua)
#dfhack_plugin(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread)
#dfhack_plugin(manipulator manipulator.cpp)
#dfhack_plugin(map-render map-render.cpp LINK_LIBRARIES lua)
dfhack_plugin(misery misery.cpp)
dfhack_plugin(misery misery.cpp LINK_LIBRARIES lua)
#dfhack_plugin(mode mode.cpp)
#dfhack_plugin(mousequery mousequery.cpp)
dfhack_plugin(nestboxes nestboxes.cpp)
@ -159,7 +159,7 @@ dfhack_plugin(showmood showmood.cpp)
#add_subdirectory(stockpiles)
#dfhack_plugin(stocks stocks.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(title-folder title-folder.cpp)
#dfhack_plugin(title-version title-version.cpp)

@ -803,6 +803,30 @@ static void push_burrow_config(lua_State *L, PersistentDataItem &c) {
get_config_bool(c, BURROW_CONFIG_PROTECT_COOKABLE));
}
static void emplace_bulk_burrow_config(lua_State *L, map<int32_t, map<string, int32_t>> &burrows, int id, bool chop = false,
bool clearcut = false, bool protect_brewable = false,
bool protect_edible = false, bool protect_cookable = false) {
map<string, int32_t> burrow_config;
burrow_config.emplace("id", id);
burrow_config.emplace("chop", chop);
burrow_config.emplace("clearcut", clearcut);
burrow_config.emplace("protect_brewable", protect_brewable);
burrow_config.emplace("protect_edible", protect_edible);
burrow_config.emplace("protect_cookable", protect_cookable);
burrows.emplace(id, burrow_config);
}
static void emplace_bulk_burrow_config(lua_State *L, map<int32_t, map<string, int32_t>> &burrows, PersistentDataItem &c) {
emplace_bulk_burrow_config(L, burrows, get_config_val(c, BURROW_CONFIG_ID),
get_config_bool(c, BURROW_CONFIG_CHOP),
get_config_bool(c, BURROW_CONFIG_CLEARCUT),
get_config_bool(c, BURROW_CONFIG_PROTECT_BREWABLE),
get_config_bool(c, BURROW_CONFIG_PROTECT_EDIBLE),
get_config_bool(c, BURROW_CONFIG_PROTECT_COOKABLE));
}
static int autochop_getTreeCountsAndBurrowConfigs(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
@ -818,6 +842,9 @@ static int autochop_getTreeCountsAndBurrowConfigs(lua_State *L) {
&designated_trees, &accessible_yield, &tree_counts, &designated_tree_counts);
map<string, int32_t> summary;
map<int32_t, map<string, int32_t>> burrow_config_map;
summary.emplace("accessible_trees", accessible_trees);
summary.emplace("inaccessible_trees", inaccessible_trees);
summary.emplace("designated_trees", designated_trees);
@ -831,13 +858,16 @@ static int autochop_getTreeCountsAndBurrowConfigs(lua_State *L) {
for (auto &burrow : plotinfo->burrows.list) {
int id = burrow->id;
if (watched_burrows_indices.count(id)) {
push_burrow_config(L, watched_burrows[watched_burrows_indices[id]]);
// push_burrow_config(L, watched_burrows[watched_burrows_indices[id]]);
emplace_bulk_burrow_config(L, burrow_config_map, watched_burrows[watched_burrows_indices[id]]);
} else {
push_burrow_config(L, id);
emplace_bulk_burrow_config(L, burrow_config_map, id);
}
}
return 3 + plotinfo->burrows.list.size();
Lua::Push(L, burrow_config_map);
return 4;
}
static int autochop_getBurrowConfig(lua_State *L) {

@ -567,6 +567,20 @@ static void push_stockpile_config(lua_State *L, PersistentDataItem &c) {
get_config_bool(c, STOCKPILE_CONFIG_MONITORED));
}
static void emplace_bulk_stockpile_config(lua_State *L, int id, bool monitored, map<int32_t, map<string, int32_t>> &stockpiles) {
map<string, int32_t> stockpile_config;
stockpile_config.emplace("id", id);
stockpile_config.emplace("monitored", monitored);
stockpiles.emplace(id, stockpile_config);
}
static void emplace_bulk_stockpile_config(lua_State *L, PersistentDataItem &c, map<int32_t, map<string, int32_t>> &stockpiles) {
int32_t id = get_config_val(c, STOCKPILE_CONFIG_ID);
bool monitored = get_config_bool(c, STOCKPILE_CONFIG_MONITORED);
emplace_bulk_stockpile_config(L, id, monitored, stockpiles);
}
static void automelt_designate(color_ostream &out) {
DEBUG(status, out).print("entering automelt designate\n");
out.print("designated %d item(s) for melting\n", do_cycle(out));
@ -762,24 +776,28 @@ static int automelt_getItemCountsAndStockpileConfigs(lua_State *L) {
Lua::Push(L, item_count_piles);
Lua::Push(L, marked_item_count_piles);
Lua::Push(L, premarked_item_count_piles);
int32_t bldg_count = 0;
map<int32_t, map<string, int32_t>> stockpile_config_map;
for (auto pile : world->buildings.other.STOCKPILE) {
if (!isStockpile(pile))
continue;
bldg_count++;
int id = pile->id;
if (watched_stockpiles.count(id)) {
DEBUG(cycle,*out).print("indexed_id=%d\n", get_config_val(watched_stockpiles[id], STOCKPILE_CONFIG_ID));
push_stockpile_config(L, watched_stockpiles[id]);
emplace_bulk_stockpile_config(L, watched_stockpiles[id], stockpile_config_map);
} else {
push_stockpile_config(L, id, false);
emplace_bulk_stockpile_config(L, id, false, stockpile_config_map);
}
}
Lua::Push(L, stockpile_config_map);
DEBUG(perf, *out).print("exit automelt_getItemCountsAndStockpileConfigs\n");
return 4+bldg_count;
return 5;
}
DFHACK_PLUGIN_LUA_FUNCTIONS{

@ -5,7 +5,17 @@
#include <modules/EventManager.h> //hash function for df::coord
#include <df/block_square_event_designation_priorityst.h>
#define NUMARGS(...) std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value
#define d_assert(condition, ...) \
static_assert(NUMARGS(__VA_ARGS__) >= 1, "d_assert(condition, format, ...) requires at least up to format as arguments"); \
if (!condition) { \
DFHack::Core::getInstance().getConsole().printerr(__VA_ARGS__); \
assert(0); \
}
df::unit* find_dwarf(const df::coord &map_pos) {
df::unit* nearest = nullptr;
uint32_t distance;
for (auto unit : df::global::world->units.active) {
@ -57,6 +67,7 @@ void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool
// cavein prevention
bool cavein_possible = false;
uint8_t least_access = 100;
std::unordered_map<df::coord, uint8_t> cavein_candidates;
if (!marker_mode) {
/* To prevent cave-ins we're looking at accessibility of tiles with open space below them
@ -111,7 +122,7 @@ void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool
// if no cave-in is possible [or we don't check for], we'll just execute normally and move on
if (!cavein_possible) {
TRACE(manager).print("cave-in evaluated false\n");
assert(manage_one(pos, true, marker_mode));
d_assert(manage_one(pos, true, marker_mode), "manage_one() is failing under !cavein");
continue;
}
// cavein is only possible if marker_mode is false
@ -136,16 +147,16 @@ void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool
evT->priority[Coord(local)] = v;
}
}
assert(manage_one(pos, true, false));
d_assert(manage_one(pos, true, false), "manage_one() is failing for cavein ");
continue;
}
// cavein possible, but we failed to meet the criteria for activation
if (cavein_candidates.count(pos)) {
DEBUG(manager).print("cave-in evaluated true and no dignow and (%d > %d)\n", cavein_candidates[pos], least_access+OFFSET);
DEBUG(manager).print("cave-in evaluated true and the cavein candidate's accessibility check was made as (%d <= %d)\n", cavein_candidates[pos], least_access+OFFSET);
} else {
DEBUG(manager).print("cave-in evaluated true and no dignow and pos is not a candidate\n");
DEBUG(manager).print("cave-in evaluated true and the position was not a candidate, nor was it set for dignow\n");
}
assert(manage_one(pos, true, true));
d_assert(manage_one(pos, true, true), "manage_one() is failing to set a cave-in causing designation to marker mode");
}
INFO(manager).print("manage_group() is done\n");
}

@ -96,14 +96,17 @@ function getTreeCountsAndBurrowConfigs()
ret.summary = table.remove(data, 1)
ret.tree_counts = table.remove(data, 1)
ret.designated_tree_counts = table.remove(data, 1)
ret.burrow_configs = data
for _,c in ipairs(ret.burrow_configs) do
local unparsed_burrow_configs = table.remove(data, 1)
ret.burrow_configs = {}
for idx,c in pairs(unparsed_burrow_configs) do
c.name = df.burrow.find(c.id).name
c.chop = c.chop ~= 0
c.clearcut = c.clearcut ~= 0
c.protect_brewable = c.protect_brewable ~= 0
c.protect_edible = c.protect_edible ~= 0
c.protect_cookable = c.protect_cookable ~= 0
table.insert(ret.burrow_configs, c)
end
return ret
end

@ -64,8 +64,10 @@ function getItemCountsAndStockpileConfigs()
ret.item_counts = table.remove(data, 1)
ret.marked_item_counts = table.remove(data, 1)
ret.premarked_item_counts = table.remove(data, 1)
ret.stockpile_configs = data
for _,c in ipairs(ret.stockpile_configs) do
local unparsed_stockpile_configs = table.remove(data, 1)
ret.stockpile_configs = {}
for idx,c in pairs(unparsed_stockpile_configs) do
if not c.id or c.id == -1 then
c.name = "ERROR"
c.monitored = false
@ -76,6 +78,7 @@ function getItemCountsAndStockpileConfigs()
end
c.monitored = c.monitored ~= 0
end
table.insert(ret.stockpile_configs, c)
end
return ret

@ -0,0 +1,43 @@
local _ENV = mkmodule('plugins.misery')
local argparse = require('argparse')
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(('misery is %s'):format(isEnabled() and "enabled" or "disabled"))
print(('misery factor is: %d'):format(misery_getFactor()))
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 == 'factor' then
misery_setFactor(positionals[1])
elseif command == 'clear' then
misery_clear()
else
return false
end
return true
end
return _ENV

@ -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(isEnabled() 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,14 +1,7 @@
#include <algorithm>
#include <map>
#include <string>
#include <vector>
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Units.h"
#include "df/emotion_type.h"
#include "df/plotinfost.h"
#include "df/unit.h"
@ -17,179 +10,266 @@
#include "df/unit_thought_type.h"
#include "df/world.h"
using namespace std;
#include "modules/Persistence.h"
#include "modules/Units.h"
#include "modules/World.h"
#include "Core.h"
#include "Debug.h"
#include "LuaTools.h"
#include "PluginManager.h"
using std::string;
using std::vector;
using namespace DFHack;
DFHACK_PLUGIN("misery");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(plotinfo);
REQUIRE_GLOBAL(cur_year);
REQUIRE_GLOBAL(cur_year_tick);
REQUIRE_GLOBAL(world);
typedef df::unit_personality::T_emotions Emotion;
static int factor = 1;
static int tick = 0;
const int INTERVAL = 1000;
namespace DFHack {
DBG_DECLARE(misery, cycle, DebugCategory::LINFO);
DBG_DECLARE(misery, config, DebugCategory::LINFO);
}
command_result misery(color_ostream& out, vector<string>& parameters);
void add_misery(df::unit *unit);
void clear_misery(df::unit *unit);
static const string CONFIG_KEY = string(plugin_name) + "/config";
static PersistentDataItem config;
const int FAKE_EMOTION_FLAG = (1 << 30);
const int STRENGTH_MULTIPLIER = 100;
enum ConfigValues {
CONFIG_IS_ENABLED = 0,
CONFIG_FACTOR = 1,
};
bool is_valid_unit (df::unit *unit) {
if (!Units::isOwnRace(unit) || !Units::isOwnCiv(unit))
return false;
if (!Units::isActive(unit))
return false;
return true;
static int get_config_val(PersistentDataItem &c, int index) {
if (!c.isValid())
return -1;
return c.ival(index);
}
inline bool is_fake_emotion (Emotion *e) {
return e->flags.whole & FAKE_EMOTION_FLAG;
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);
}
void add_misery (df::unit *unit) {
// Add a fake miserable thought
// Remove any fake ones that already exist
if (!unit || !unit->status.current_soul)
return;
clear_misery(unit);
auto &emotions = unit->status.current_soul->personality.emotions;
Emotion *e = new Emotion;
e->type = df::emotion_type::MISERY;
e->thought = df::unit_thought_type::SoapyBath;
e->flags.whole |= FAKE_EMOTION_FLAG;
emotions.push_back(e);
static const int32_t CYCLE_TICKS = 1200; // one day
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
for (Emotion *e : emotions) {
if (is_fake_emotion(e)) {
e->year = *cur_year;
e->year_tick = *cur_year_tick;
e->strength = STRENGTH_MULTIPLIER * factor;
e->severity = STRENGTH_MULTIPLIER * factor;
}
static command_result do_command(color_ostream &out, vector<string> &parameters);
static void do_cycle(color_ostream &out);
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
DEBUG(config,out).print("initializing %s\n", plugin_name);
// provide a configuration interface for the plugin
commands.push_back(PluginCommand(
plugin_name,
"Increase the intensity of negative dwarven thoughts.",
do_command));
return CR_OK;
}
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;
}
void clear_misery (df::unit *unit) {
if (!unit || !unit->status.current_soul)
return;
auto &emotions = unit->status.current_soul->personality.emotions;
auto it = remove_if(emotions.begin(), emotions.end(), [](Emotion *e) {
if (is_fake_emotion(e)) {
delete e;
return true;
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 false;
});
emotions.erase(it, emotions.end());
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out) {
factor = 0;
return CR_OK;
}
DEBUG(config,out).print("shutting down %s\n", plugin_name);
DFhackCExport command_result plugin_onupdate(color_ostream& out) {
static bool wasLoaded = false;
if ( factor == 0 || !world || !world->map.block_index ) {
if ( wasLoaded ) {
//we just unloaded the game: clear all data
factor = 0;
is_enabled = false;
wasLoaded = false;
}
return CR_OK;
}
if ( !wasLoaded ) {
wasLoaded = true;
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);
set_config_val(config, CONFIG_FACTOR, 2);
}
if ( tick < INTERVAL ) {
tick++;
is_enabled = get_config_bool(config, CONFIG_IS_ENABLED);
DEBUG(config,out).print("loading persisted enabled state: %s\n",
is_enabled ? "true" : "false");
return CR_OK;
}
tick = 0;
//TODO: consider units.active
for (df::unit *unit : world->units.all) {
if (is_valid_unit(unit)) {
add_misery(unit);
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
if (event == DFHack::SC_WORLD_UNLOADED) {
if (is_enabled) {
DEBUG(config,out).print("world unloaded; disabling %s\n",
plugin_name);
is_enabled = false;
}
}
return CR_OK;
}
DFhackCExport command_result plugin_init(color_ostream& out, vector<PluginCommand> &commands) {
commands.push_back(PluginCommand(
"misery",
"Increase the intensity of negative dwarven thoughts.",
misery));
DFhackCExport command_result plugin_onupdate(color_ostream &out) {
if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS)
do_cycle(out);
return CR_OK;
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
if (enable != is_enabled)
{
is_enabled = enable;
factor = enable ? 1 : 0;
tick = INTERVAL;
static bool call_misery_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 misery lua function: '%s'\n", fn_name);
CoreSuspender guard;
auto L = Lua::Core::State;
Lua::StackUnwinder top(L);
if (!out)
out = &Core::getInstance().getConsole();
return Lua::CallLuaModuleFunction(*out, L, "plugins.misery", fn_name,
nargs, nres,
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;
if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
return CR_FAILURE;
}
command_result misery(color_ostream &out, vector<string>& parameters) {
if ( !world || !world->map.block_index ) {
out.printerr("misery can only be enabled in fortress mode with a fully-loaded game.\n");
bool show_help = false;
if (!call_misery_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;
}
if ( parameters.size() < 1 || parameters.size() > 2 ) {
return CR_WRONG_USAGE;
return show_help ? CR_WRONG_USAGE : CR_OK;
}
if ( parameters[0] == "disable" ) {
if ( parameters.size() > 1 ) {
return CR_WRONG_USAGE;
/////////////////////////////////////////////////////
// cycle logic
//
const int FAKE_EMOTION_FLAG = (1 << 30);
const int STRENGTH_MULTIPLIER = 100;
typedef df::unit_personality::T_emotions Emotion;
static bool is_fake_emotion(Emotion *e) {
return e->flags.whole & FAKE_EMOTION_FLAG;
}
factor = 0;
is_enabled = false;
return CR_OK;
} else if ( parameters[0] == "enable" ) {
is_enabled = true;
factor = 1;
if ( parameters.size() == 2 ) {
int a = atoi(parameters[1].c_str());
if ( a < 1 ) {
out.printerr("Second argument must be a positive integer.\n");
return CR_WRONG_USAGE;
}
factor = a;
}
tick = INTERVAL;
} else if ( parameters[0] == "clear" ) {
for (df::unit *unit : world->units.all) {
if (is_valid_unit(unit)) {
static void clear_misery(df::unit *unit) {
if (!unit || !unit->status.current_soul)
return;
auto &emotions = unit->status.current_soul->personality.emotions;
auto it = std::remove_if(emotions.begin(), emotions.end(), [](Emotion *e) {
if (is_fake_emotion(e)) {
delete e;
return true;
}
return false;
});
emotions.erase(it, emotions.end());
}
// clears fake negative thoughts then runs the given lambda
static void affect_units(
std::function<void(df::unit *)> &&process_unit = [](df::unit *){}) {
for (auto unit : world->units.active) {
if (!Units::isCitizen(unit) || !unit->status.current_soul)
continue;
clear_misery(unit);
std::forward<std::function<void(df::unit *)> &&>(process_unit)(unit);
}
}
} else {
int a = atoi(parameters[0].c_str());
if ( a < 0 ) {
return CR_WRONG_USAGE;
static void 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);
int strength = STRENGTH_MULTIPLIER * get_config_val(config, CONFIG_FACTOR);
affect_units([&](df::unit *unit) {
Emotion *e = new Emotion;
e->type = df::emotion_type::MISERY;
e->thought = df::unit_thought_type::SoapyBath;
e->flags.whole |= FAKE_EMOTION_FLAG;
e->year = *cur_year;
e->year_tick = *cur_year_tick;
e->strength = strength;
e->severity = strength;
unit->status.current_soul->personality.emotions.push_back(e);
});
}
/////////////////////////////////////////////////////
// Lua API
//
static void misery_clear(color_ostream &out) {
DEBUG(config,out).print("entering misery_clear\n");
affect_units();
}
factor = a;
is_enabled = factor > 0;
static void misery_setFactor(color_ostream &out, int32_t factor) {
DEBUG(config,out).print("entering misery_setFactor\n");
if (1 >= factor) {
out.printerr("factor must be at least 2\n");
return;
}
set_config_val(config, CONFIG_FACTOR, factor);
if (is_enabled)
do_cycle(out);
}
return CR_OK;
static int misery_getFactor(color_ostream &out) {
DEBUG(config,out).print("entering tailor_getFactor\n");
return get_config_val(config, CONFIG_FACTOR);
}
DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(misery_clear),
DFHACK_LUA_FUNCTION(misery_setFactor),
DFHACK_LUA_FUNCTION(misery_getFactor),
DFHACK_LUA_END
};

@ -1,61 +1,84 @@
/*
* 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 "DataDefs.h"
#include "Debug.h"
#include "PluginManager.h"
#include <string>
#include <vector>
#include "df/creature_raw.h"
#include "df/global_objects.h"
#include "df/historical_entity.h"
#include "df/item.h"
#include "df/item_flags.h"
#include "df/itemdef_armorst.h"
#include "df/itemdef_glovesst.h"
#include "df/itemdef_helmst.h"
#include "df/itemdef_pantsst.h"
#include "df/itemdef_shoesst.h"
#include "df/items_other_id.h"
#include "df/job.h"
#include "df/job_type.h"
#include "df/manager_order.h"
#include "df/plotinfost.h"
#include "df/world.h"
#include "modules/Maps.h"
#include "modules/Units.h"
#include "Core.h"
#include "Debug.h"
#include "LuaTools.h"
#include "PluginManager.h"
#include "modules/Materials.h"
#include "modules/Persistence.h"
#include "modules/Translation.h"
#include "modules/Units.h"
#include "modules/World.h"
using namespace DFHack;
using std::string;
using std::vector;
using df::global::world;
using df::global::plotinfo;
using namespace DFHack;
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(standing_orders_use_dyed_cloth);
REQUIRE_GLOBAL(world);
namespace DFHack {
DBG_DECLARE(tailor, cycle, DebugCategory::LINFO);
DBG_DECLARE(tailor, config, DebugCategory::LINFO);
}
class Tailor {
// ARMOR, SHOES, HELM, GLOVES, PANTS
static const string CONFIG_KEY = string(plugin_name) + "/config";
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::MakePants, df::item_type::PANTS },
{ df::job_type::MakeHelm, df::item_type::HELM },
@ -63,7 +86,7 @@ private:
{ 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::PANTS, df::job_type::MakePants },
{ df::item_type::HELM, df::job_type::MakeHelm },
@ -71,31 +94,18 @@ private:
{ 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;
const std::string name;
const df::job_material_category job_material;
const df::armor_general_flags armor_flag;
bool operator==(const MatType& m) const
{
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
{
bool operator<(const MatType& m) const {
return name < m.name;
}
@ -103,34 +113,48 @@ private:
: 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) {};
};
const MatType
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);
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> needed; // 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<std::tuple<df::job_type, int, int>, int> orders; // key is item type, item subtype, size
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;
int default_reserve = 10;
public:
void reset()
{
available.clear();
@ -145,9 +169,7 @@ private:
{
for (auto i : world->items.other[df::items_other_id::ANY_GENERIC37]) // GENERIC37 is "clothing"
{
if (i->flags.whole & bad_flags.whole)
continue;
if (i->flags.bits.owned)
if (i->flags.whole & badFlags.whole)
continue;
if (i->getWear() >= 1)
continue;
@ -164,7 +186,7 @@ private:
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;
if (require_dyed && !i->hasImprovements())
@ -197,7 +219,7 @@ private:
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;
supply[M_LEATHER] += i->getStackSize();
}
@ -369,8 +391,9 @@ private:
}
void place_orders()
int place_orders()
{
int ordered = 0;
auto entity = world->entities.all[plotinfo->civ_id];
for (auto& o : orders)
@ -477,6 +500,7 @@ private:
);
count -= c;
ordered += c;
}
else
{
@ -486,215 +510,217 @@ private:
}
}
}
return ordered;
}
};
public:
void do_scan(color_ostream& o)
{
out = &o;
reset();
// scan for useable clothing
scan_clothing();
static std::unique_ptr<Tailor> tailor_instance;
// 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:
command_result set_materials(color_ostream& out, std::vector<std::string>& parameters)
{
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;
static void set_material_order() {
material_order.clear();
for (size_t i = 0; i < all_materials.size(); ++i) {
if (i == (size_t)get_config_val(config, CONFIG_SILK_IDX))
material_order.push_back(M_SILK);
else if (i == (size_t)get_config_val(config, CONFIG_CLOTH_IDX))
material_order.push_back(M_CLOTH);
else if (i == (size_t)get_config_val(config, CONFIG_YARN_IDX))
material_order.push_back(M_YARN);
else if (i == (size_t)get_config_val(config, CONFIG_LEATHER_IDX))
material_order.push_back(M_LEATHER);
}
else {
newmat.push_back(*mm);
if (!material_order.size())
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;
INFO(config,out).print("tailor: material list set to %s\n", get_material_list().c_str());
is_enabled = get_config_bool(config, CONFIG_IS_ENABLED);
DEBUG(config,out).print("loading persisted enabled state: %s\n",
is_enabled ? "true" : "false");
set_material_order();
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;
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
if (event == DFHack::SC_WORLD_UNLOADED) {
if (is_enabled) {
DEBUG(config,out).print("world unloaded; disabling %s\n",
plugin_name);
is_enabled = false;
}
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)
{
do_scan(out);
DFhackCExport command_result plugin_onupdate(color_ostream &out) {
if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS) {
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;
#define DELTA_TICKS 50
DFhackCExport command_result plugin_onupdate(color_ostream& out)
{
if (!enabled || !tailor_instance)
return CR_OK;
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);
if (!Maps::IsValid())
return CR_OK;
CoreSuspender guard;
if (DFHack::World::ReadPauseState())
return CR_OK;
auto L = Lua::Core::State;
Lua::StackUnwinder top(L);
if (world->frame_counter % DELTA_TICKS != 0)
return CR_OK;
if (!out)
out = &Core::getInstance().getConsole();
{
CoreSuspender suspend;
tailor_instance->process(out);
return Lua::CallLuaModuleFunction(*out, L, "plugins.tailor", fn_name,
nargs, nres,
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) {
bool desired = enabled;
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);
if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot run %s without a loaded world.\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);
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;
}
}
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);
return show_help ? CR_WRONG_USAGE : CR_OK;
}
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)
{
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();
}
DFhackCExport command_result plugin_enable(color_ostream& out, bool enable)
{
enabled = enable;
return CR_OK;
/////////////////////////////////////////////////////
// Lua API
//
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)
{
tailor_instance = std::move(dts::make_unique<Tailor>());
// remember, these are ONE-based indices from Lua
static void tailor_setMaterialPreferences(color_ostream &out, int32_t silkIdx,
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 - 1);
set_config_val(config, CONFIG_CLOTH_IDX, clothIdx - 1);
set_config_val(config, CONFIG_YARN_IDX, yarnIdx - 1);
set_config_val(config, CONFIG_LEATHER_IDX, leatherIdx - 1);
if (AUTOENABLE) {
enabled = true;
set_material_order();
}
commands.push_back(PluginCommand(
plugin_name,
"Automatically keep your dwarves in fresh clothing.",
tailor_cmd));
return CR_OK;
static int tailor_getMaterialPreferences(lua_State *L) {
color_ostream *out = Lua::GetOutput(L);
if (!out)
out = &Core::getInstance().getConsole();
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)
{
tailor_instance.release();
DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(tailor_doCycle),
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
};

@ -1 +1 @@
Subproject commit 112d69ed3e0efb22e3ec6a6c8e58ad3135b80351
Subproject commit 8807e7a7845611b2cc613fb3903f6f8763e91300