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() endif()
# set up versioning. # set up versioning.
set(DF_VERSION "50.05") set(DF_VERSION "50.07")
set(DFHACK_RELEASE "alpha3.1") set(DFHACK_RELEASE "alpha0")
set(DFHACK_PRERELEASE TRUE) set(DFHACK_PRERELEASE TRUE)
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") 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", "job" : "MakeWeapon",
"material" : "INORGANIC:SILVER" "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_left" : 1,
"amount_total" : 1, "amount_total" : 1,
@ -1656,12 +1620,6 @@
"item_type" : "WEAPON", "item_type" : "WEAPON",
"material" : "INORGANIC:STEEL", "material" : "INORGANIC:STEEL",
"value" : 10 "value" : 10
},
{
"condition" : "LessThan",
"item_type" : "BAR",
"material" : "INORGANIC:SILVER",
"value" : 5
} }
], ],
"item_subtype" : "ITEM_WEAPON_CROSSBOW", "item_subtype" : "ITEM_WEAPON_CROSSBOW",
@ -2357,12 +2315,6 @@
"material" : "INORGANIC:STEEL", "material" : "INORGANIC:STEEL",
"value" : 30 "value" : 30
}, },
{
"condition" : "LessThan",
"item_type" : "BAR",
"material" : "INORGANIC:SILVER",
"value" : 5
},
{ {
"condition" : "AtMost", "condition" : "AtMost",
"flags" : "flags" :
@ -3068,12 +3020,6 @@
"material" : "INORGANIC:STEEL", "material" : "INORGANIC:STEEL",
"value" : 30 "value" : 30
}, },
{
"condition" : "LessThan",
"item_type" : "BAR",
"material" : "INORGANIC:SILVER",
"value" : 5
},
{ {
"condition" : "AtMost", "condition" : "AtMost",
"flags" : "flags" :
@ -3856,12 +3802,6 @@
"material" : "INORGANIC:STEEL", "material" : "INORGANIC:STEEL",
"value" : 30 "value" : 30
}, },
{
"condition" : "LessThan",
"item_type" : "BAR",
"material" : "INORGANIC:SILVER",
"value" : 5
},
{ {
"condition" : "AtMost", "condition" : "AtMost",
"flags" : "flags" :
@ -4734,12 +4674,6 @@
"material" : "INORGANIC:STEEL", "material" : "INORGANIC:STEEL",
"value" : 30 "value" : 30
}, },
{
"condition" : "LessThan",
"item_type" : "BAR",
"material" : "INORGANIC:SILVER",
"value" : 5
},
{ {
"condition" : "AtMost", "condition" : "AtMost",
"flags" : "flags" :

@ -39,16 +39,22 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- ``Units::isFortControlled``: Account for agitated wildlife - ``Units::isFortControlled``: Account for agitated wildlife
- Fix right click sometimes closing both a DFHack window and a vanilla panel - 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 - 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 ## Misc Improvements
- `automelt`: is now more resistent to savegame corruption - `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`: reinstate ``autodump-destroy-item``, hotkey: Ctrl-K
- `autodump`: new hotkey for ``autodump-destroy-here``: Ctrl-H - `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) - `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 - `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`: 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 - `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 ## Documentation
@ -57,13 +63,16 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Lua ## 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. - `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.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 ## Removed
# 50.05-alpha3.1 # 50.05-alpha3.1
## Fixes ## Fixes
- `seedwatch`: fix parameter parsing when setting targets -@ `seedwatch`: fix parameter parsing when setting targets
# 50.05-alpha3 # 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 :on_activate: If specified, it is the callback that will be called whenever
the hotkey is pressed or the label is clicked. 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 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 In addition to passing through all attributes supported by List, it
supports: 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_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_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. :edit_key: If specified, the edit field is disabled until this key is pressed.

@ -2,18 +2,35 @@ misery
====== ======
.. dfhack-tool:: .. dfhack-tool::
:summary: Increase the intensity of negative dwarven thoughts. :summary: Increase the intensity of your citizens' negative thoughts.
:tags: fort armok auto units :tags: fort gameplay units
When enabled, negative thoughts that your dwarves have will multiply by the When enabled, negative thoughts that your citizens have will multiply by the
specified factor. specified factor. This makes it more challenging to keep them happy.
Usage 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`` ``enable misery``
Start multiplying negative thoughts. Start multiplying bad thoughts for your citizens!
``misery <factor>``
Change the multiplicative factor of bad thoughts. The default is ``2``. ``misery 5``
Make dwarves become unhappy 5 times faster than normal -- this is quite
challenging to handle!
``misery clear`` ``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. :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

@ -478,7 +478,7 @@ static void OpenPersistent(lua_State *state)
static int DFHACK_MATINFO_TOKEN = 0; 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()) if (!info.isValid())
{ {

@ -101,7 +101,7 @@ void DFHack::Lua::Push(lua_State *state, const Units::NoblePosition &pos)
lua_setfield(state, -2, "position"); 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_createtable(state, 0, 3);
lua_pushinteger(state, pos.x); lua_pushinteger(state, pos.x);
@ -112,7 +112,7 @@ void DFHack::Lua::Push(lua_State *state, df::coord pos)
lua_setfield(state, -2, "z"); 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_createtable(state, 0, 2);
lua_pushinteger(state, pos.x); 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()) 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()) if (!pos.isValid())
{ {

@ -325,10 +325,10 @@ namespace DFHack {namespace Lua {
inline void Push(lua_State *state, const std::string &str) { inline void Push(lua_State *state, const std::string &str) {
lua_pushlstring(state, str.data(), str.size()); lua_pushlstring(state, str.data(), str.size());
} }
DFHACK_EXPORT void Push(lua_State *state, df::coord obj); DFHACK_EXPORT void Push(lua_State *state, const df::coord &obj);
DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj); DFHACK_EXPORT void Push(lua_State *state, const df::coord2d &obj);
void Push(lua_State *state, const Units::NoblePosition &pos); 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); DFHACK_EXPORT void Push(lua_State *state, const Screen::Pen &info);
template<class T> inline void Push(lua_State *state, T *ptr) { template<class T> inline void Push(lua_State *state, T *ptr) {
PushDFObject(state, 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 void GetVector(lua_State *state, std::vector<std::string> &pvec);
DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos); DFHACK_EXPORT int PushPosXYZ(lua_State *state, const df::coord &pos);
DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos); DFHACK_EXPORT int PushPosXY(lua_State *state, const 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);
}
template<typename T_Key, typename T_Value> template<typename T_Key, typename T_Value>
void Push(lua_State *L, const std::map<T_Key, T_Value> &pmap) { void Push(lua_State *L, const std::map<T_Key, T_Value> &pmap) {
lua_createtable(L, 0, pmap.size()); lua_createtable(L, 0, pmap.size());
for (auto &entry : pmap) for (auto &entry : pmap) {
TableInsert(L, entry.first, entry.second); Lua::Push(L, entry.first);
Lua::Push(L, entry.second);
lua_settable(L, -3);
}
} }
template<typename T_Key, typename T_Value> template<typename T_Key, typename T_Value>
void Push(lua_State *L, const std::unordered_map<T_Key, T_Value> &pmap) { void Push(lua_State *L, const std::unordered_map<T_Key, T_Value> &pmap) {
lua_createtable(L, 0, pmap.size()); lua_createtable(L, 0, pmap.size());
for (auto &entry : pmap) for (auto &entry : pmap) {
TableInsert(L, entry.first, entry.second); 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); 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() 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, self:setText{{key=self.key, key_sep=self.key_sep, text=self.label,
on_activate=self.on_activate}} on_activate=self.on_activate}}
end end
@ -1868,6 +1882,7 @@ end
FilteredList = defclass(FilteredList, Widget) FilteredList = defclass(FilteredList, Widget)
FilteredList.ATTRS { FilteredList.ATTRS {
case_sensitive = true,
edit_below = false, edit_below = false,
edit_key = DEFAULT_NIL, edit_key = DEFAULT_NIL,
edit_ignore_keys = DEFAULT_NIL, edit_ignore_keys = DEFAULT_NIL,
@ -2028,11 +2043,17 @@ function FilteredList:setFilter(filter, pos)
-- start matches at non-space or non-punctuation. this allows -- start matches at non-space or non-punctuation. this allows
-- punctuation itself to be matched if that is useful (e.g. -- punctuation itself to be matched if that is useful (e.g.
-- filenames or parameter names) -- filenames or parameter names)
if key ~= '' and if key ~= '' then
not search_key:match('%f[^%p\x00]'..key) and 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 not search_key:match('%f[^%s\x00]'..key) then
ok = false ok = false
break break
end
end end
end end
if ok then if ok then

@ -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(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread)
#dfhack_plugin(manipulator manipulator.cpp) #dfhack_plugin(manipulator manipulator.cpp)
#dfhack_plugin(map-render map-render.cpp LINK_LIBRARIES lua) #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(mode mode.cpp)
#dfhack_plugin(mousequery mousequery.cpp) #dfhack_plugin(mousequery mousequery.cpp)
dfhack_plugin(nestboxes nestboxes.cpp) dfhack_plugin(nestboxes nestboxes.cpp)
@ -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)

@ -803,6 +803,30 @@ static void push_burrow_config(lua_State *L, PersistentDataItem &c) {
get_config_bool(c, BURROW_CONFIG_PROTECT_COOKABLE)); 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) { static int autochop_getTreeCountsAndBurrowConfigs(lua_State *L) {
color_ostream *out = Lua::GetOutput(L); color_ostream *out = Lua::GetOutput(L);
if (!out) if (!out)
@ -818,6 +842,9 @@ static int autochop_getTreeCountsAndBurrowConfigs(lua_State *L) {
&designated_trees, &accessible_yield, &tree_counts, &designated_tree_counts); &designated_trees, &accessible_yield, &tree_counts, &designated_tree_counts);
map<string, int32_t> summary; map<string, int32_t> summary;
map<int32_t, map<string, int32_t>> burrow_config_map;
summary.emplace("accessible_trees", accessible_trees); summary.emplace("accessible_trees", accessible_trees);
summary.emplace("inaccessible_trees", inaccessible_trees); summary.emplace("inaccessible_trees", inaccessible_trees);
summary.emplace("designated_trees", designated_trees); summary.emplace("designated_trees", designated_trees);
@ -831,13 +858,16 @@ static int autochop_getTreeCountsAndBurrowConfigs(lua_State *L) {
for (auto &burrow : plotinfo->burrows.list) { for (auto &burrow : plotinfo->burrows.list) {
int id = burrow->id; int id = burrow->id;
if (watched_burrows_indices.count(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 { } 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) { 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)); 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) { static void automelt_designate(color_ostream &out) {
DEBUG(status, out).print("entering automelt designate\n"); DEBUG(status, out).print("entering automelt designate\n");
out.print("designated %d item(s) for melting\n", do_cycle(out)); 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, item_count_piles);
Lua::Push(L, marked_item_count_piles); Lua::Push(L, marked_item_count_piles);
Lua::Push(L, premarked_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) { for (auto pile : world->buildings.other.STOCKPILE) {
if (!isStockpile(pile)) if (!isStockpile(pile))
continue; continue;
bldg_count++;
int id = pile->id; int id = pile->id;
if (watched_stockpiles.count(id)) { if (watched_stockpiles.count(id)) {
DEBUG(cycle,*out).print("indexed_id=%d\n", get_config_val(watched_stockpiles[id], STOCKPILE_CONFIG_ID)); emplace_bulk_stockpile_config(L, watched_stockpiles[id], stockpile_config_map);
push_stockpile_config(L, watched_stockpiles[id]);
} else { } 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"); DEBUG(perf, *out).print("exit automelt_getItemCountsAndStockpileConfigs\n");
return 4+bldg_count; return 5;
} }
DFHACK_PLUGIN_LUA_FUNCTIONS{ DFHACK_PLUGIN_LUA_FUNCTIONS{

@ -5,7 +5,17 @@
#include <modules/EventManager.h> //hash function for df::coord #include <modules/EventManager.h> //hash function for df::coord
#include <df/block_square_event_designation_priorityst.h> #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* find_dwarf(const df::coord &map_pos) {
df::unit* nearest = nullptr; df::unit* nearest = nullptr;
uint32_t distance; uint32_t distance;
for (auto unit : df::global::world->units.active) { 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 // cavein prevention
bool cavein_possible = false; bool cavein_possible = false;
uint8_t least_access = 100; uint8_t least_access = 100;
std::unordered_map<df::coord, uint8_t> cavein_candidates; std::unordered_map<df::coord, uint8_t> cavein_candidates;
if (!marker_mode) { if (!marker_mode) {
/* To prevent cave-ins we're looking at accessibility of tiles with open space below them /* 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 no cave-in is possible [or we don't check for], we'll just execute normally and move on
if (!cavein_possible) { if (!cavein_possible) {
TRACE(manager).print("cave-in evaluated false\n"); 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; continue;
} }
// cavein is only possible if marker_mode is false // 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; 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; continue;
} }
// cavein possible, but we failed to meet the criteria for activation // cavein possible, but we failed to meet the criteria for activation
if (cavein_candidates.count(pos)) { 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 { } 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"); INFO(manager).print("manage_group() is done\n");
} }

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

@ -64,8 +64,10 @@ function getItemCountsAndStockpileConfigs()
ret.item_counts = table.remove(data, 1) ret.item_counts = table.remove(data, 1)
ret.marked_item_counts = table.remove(data, 1) ret.marked_item_counts = table.remove(data, 1)
ret.premarked_item_counts = table.remove(data, 1) ret.premarked_item_counts = table.remove(data, 1)
ret.stockpile_configs = data local unparsed_stockpile_configs = table.remove(data, 1)
for _,c in ipairs(ret.stockpile_configs) do ret.stockpile_configs = {}
for idx,c in pairs(unparsed_stockpile_configs) do
if not c.id or c.id == -1 then if not c.id or c.id == -1 then
c.name = "ERROR" c.name = "ERROR"
c.monitored = false c.monitored = false
@ -76,6 +78,7 @@ function getItemCountsAndStockpileConfigs()
end end
c.monitored = c.monitored ~= 0 c.monitored = c.monitored ~= 0
end end
table.insert(ret.stockpile_configs, c)
end end
return ret 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 <algorithm>
#include <map>
#include <string> #include <string>
#include <vector> #include <vector>
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Units.h"
#include "df/emotion_type.h" #include "df/emotion_type.h"
#include "df/plotinfost.h" #include "df/plotinfost.h"
#include "df/unit.h" #include "df/unit.h"
@ -17,179 +10,266 @@
#include "df/unit_thought_type.h" #include "df/unit_thought_type.h"
#include "df/world.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; using namespace DFHack;
DFHACK_PLUGIN("misery"); DFHACK_PLUGIN("misery");
DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(plotinfo);
REQUIRE_GLOBAL(cur_year); REQUIRE_GLOBAL(cur_year);
REQUIRE_GLOBAL(cur_year_tick); REQUIRE_GLOBAL(cur_year_tick);
REQUIRE_GLOBAL(world);
typedef df::unit_personality::T_emotions Emotion; namespace DFHack {
DBG_DECLARE(misery, cycle, DebugCategory::LINFO);
static int factor = 1; DBG_DECLARE(misery, config, DebugCategory::LINFO);
static int tick = 0; }
const int INTERVAL = 1000;
command_result misery(color_ostream& out, vector<string>& parameters); static const string CONFIG_KEY = string(plugin_name) + "/config";
void add_misery(df::unit *unit); static PersistentDataItem config;
void clear_misery(df::unit *unit);
const int FAKE_EMOTION_FLAG = (1 << 30); enum ConfigValues {
const int STRENGTH_MULTIPLIER = 100; CONFIG_IS_ENABLED = 0,
CONFIG_FACTOR = 1,
};
bool is_valid_unit (df::unit *unit) { static int get_config_val(PersistentDataItem &c, int index) {
if (!Units::isOwnRace(unit) || !Units::isOwnCiv(unit)) if (!c.isValid())
return false; return -1;
if (!Units::isActive(unit)) return c.ival(index);
return false; }
return true; 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);
} }
inline bool is_fake_emotion (Emotion *e) { static const int32_t CYCLE_TICKS = 1200; // one day
return e->flags.whole & FAKE_EMOTION_FLAG; static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
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;
} }
void add_misery (df::unit *unit) { DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
// Add a fake miserable thought if (!Core::getInstance().isWorldLoaded()) {
// Remove any fake ones that already exist out.printerr("Cannot enable %s without a loaded world.\n", plugin_name);
if (!unit || !unit->status.current_soul) return CR_FAILURE;
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);
for (Emotion *e : emotions) { if (enable != is_enabled) {
if (is_fake_emotion(e)) { is_enabled = enable;
e->year = *cur_year; DEBUG(config,out).print("%s from the API; persisting\n",
e->year_tick = *cur_year_tick; is_enabled ? "enabled" : "disabled");
e->strength = STRENGTH_MULTIPLIER * factor; set_config_bool(config, CONFIG_IS_ENABLED, is_enabled);
e->severity = STRENGTH_MULTIPLIER * factor; 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;
} }
void clear_misery (df::unit *unit) { DFhackCExport command_result plugin_shutdown (color_ostream &out) {
if (!unit || !unit->status.current_soul) DEBUG(config,out).print("shutting down %s\n", plugin_name);
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;
}
return false;
});
emotions.erase(it, emotions.end());
}
DFhackCExport command_result plugin_shutdown(color_ostream& out) {
factor = 0;
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_onupdate(color_ostream& out) { DFhackCExport command_result plugin_load_data (color_ostream &out) {
static bool wasLoaded = false; cycle_timestamp = 0;
if ( factor == 0 || !world || !world->map.block_index ) { config = World::GetPersistentData(CONFIG_KEY);
if ( wasLoaded ) {
//we just unloaded the game: clear all data
factor = 0;
is_enabled = false;
wasLoaded = false;
}
return CR_OK;
}
if ( !wasLoaded ) { if (!config.isValid()) {
wasLoaded = true; 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 ) { is_enabled = get_config_bool(config, CONFIG_IS_ENABLED);
tick++; DEBUG(config,out).print("loading persisted enabled state: %s\n",
return CR_OK; is_enabled ? "true" : "false");
}
tick = 0;
//TODO: consider units.active return CR_OK;
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; return CR_OK;
} }
DFhackCExport command_result plugin_init(color_ostream& out, vector<PluginCommand> &commands) { DFhackCExport command_result plugin_onupdate(color_ostream &out) {
commands.push_back(PluginCommand( if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS)
"misery", do_cycle(out);
"Increase the intensity of negative dwarven thoughts.",
misery));
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) static bool call_misery_lua(color_ostream *out, const char *fn_name,
{ int nargs = 0, int nres = 0,
if (enable != is_enabled) Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA,
{ Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) {
is_enabled = enable; DEBUG(config).print("calling misery lua function: '%s'\n", fn_name);
factor = enable ? 1 : 0;
tick = INTERVAL;
}
return CR_OK; 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));
} }
command_result misery(color_ostream &out, vector<string>& parameters) { static command_result do_command(color_ostream &out, vector<string> &parameters) {
if ( !world || !world->map.block_index ) { CoreSuspender suspend;
out.printerr("misery can only be enabled in fortress mode with a fully-loaded game.\n");
if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
return CR_FAILURE; return CR_FAILURE;
} }
if ( parameters.size() < 1 || parameters.size() > 2 ) { bool show_help = false;
return CR_WRONG_USAGE; 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[0] == "disable" ) { return show_help ? CR_WRONG_USAGE : CR_OK;
if ( parameters.size() > 1 ) { }
return CR_WRONG_USAGE;
} /////////////////////////////////////////////////////
factor = 0; // cycle logic
is_enabled = false; //
return CR_OK;
} else if ( parameters[0] == "enable" ) { const int FAKE_EMOTION_FLAG = (1 << 30);
is_enabled = true; const int STRENGTH_MULTIPLIER = 100;
factor = 1;
if ( parameters.size() == 2 ) { typedef df::unit_personality::T_emotions Emotion;
int a = atoi(parameters[1].c_str());
if ( a < 1 ) { static bool is_fake_emotion(Emotion *e) {
out.printerr("Second argument must be a positive integer.\n"); return e->flags.whole & FAKE_EMOTION_FLAG;
return CR_WRONG_USAGE; }
}
factor = a; static void clear_misery(df::unit *unit) {
} if (!unit || !unit->status.current_soul)
tick = INTERVAL; return;
} else if ( parameters[0] == "clear" ) { auto &emotions = unit->status.current_soul->personality.emotions;
for (df::unit *unit : world->units.all) { auto it = std::remove_if(emotions.begin(), emotions.end(), [](Emotion *e) {
if (is_valid_unit(unit)) { if (is_fake_emotion(e)) {
clear_misery(unit); delete e;
} return true;
}
} else {
int a = atoi(parameters[0].c_str());
if ( a < 0 ) {
return CR_WRONG_USAGE;
} }
factor = a; return false;
is_enabled = factor > 0; });
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);
} }
}
return CR_OK; 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();
}
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);
}
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,136 +1,160 @@
/* /*
* 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 <vector>
#include "Debug.h"
#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 +169,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 +186,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 +219,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 +391,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 +500,7 @@ private:
); );
count -= c; count -= c;
ordered += c;
} }
else else
{ {
@ -486,215 +510,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 (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);
} }
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 - 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) { 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
};

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