From ba5aaf897a3b50eee328857d6574dc83317b6744 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 21 Apr 2022 07:17:14 +0000 Subject: [PATCH 001/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index cb9ec474d..ca963c935 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit cb9ec474da608db8c7a53fda9bcb921d23c6cc13 +Subproject commit ca963c935c3375a1cf3076b066df44867edf1166 From 9c9a7ef99abf773bfc7b2e52c7ff33e10dc03c16 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 21 Apr 2022 18:13:23 -0700 Subject: [PATCH 002/854] Adds consistent interface signatures for Maps module --- library/include/modules/Maps.h | 14 ++++--- library/modules/Maps.cpp | 67 +++++++++++++++++++++++++++------- 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index bc6298601..225efae17 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -233,7 +233,8 @@ extern DFHACK_EXPORT bool ReadGeology(std::vector > *layer_ /** * Get pointers to features of a block */ -extern DFHACK_EXPORT bool ReadFeatures(uint32_t x, uint32_t y, uint32_t z, t_feature * local, t_feature * global); +extern DFHACK_EXPORT bool ReadFeatures(int32_t x, int32_t y, int32_t z, t_feature * local, t_feature * global); +extern DFHACK_EXPORT bool ReadFeatures(uint32_t x, uint32_t y, uint32_t z, t_feature * local, t_feature * global); // todo: deprecate me /** * Get pointers to features of an already read block */ @@ -242,7 +243,7 @@ extern DFHACK_EXPORT bool ReadFeatures(df::map_block * block,t_feature * local, /** * Get a pointer to a specific global feature directly. - */ +*/ DFHACK_EXPORT df::feature_init *getGlobalInitFeature(int32_t index); /** * Get a pointer to a specific local feature directly. rgn_coord is in the world region grid. @@ -261,9 +262,11 @@ extern DFHACK_EXPORT bool GetGlobalFeature(t_feature &feature, int32_t index); */ /// get size of the map in blocks -extern DFHACK_EXPORT void getSize(uint32_t& x, uint32_t& y, uint32_t& z); +extern DFHACK_EXPORT void getSize(int32_t& x, int32_t& y, int32_t& z); +extern DFHACK_EXPORT void getSize(uint32_t& x, uint32_t& y, uint32_t& z); // todo: deprecate me /// get size of the map in tiles -extern DFHACK_EXPORT void getTileSize(uint32_t& x, uint32_t& y, uint32_t& z); +extern DFHACK_EXPORT void getTileSize(int32_t& x, int32_t& y, int32_t& z); +extern DFHACK_EXPORT void getTileSize(uint32_t& x, uint32_t& y, uint32_t& z); // todo: deprecate me /// get the position of the map on world map extern DFHACK_EXPORT void getPosition(int32_t& x, int32_t& y, int32_t& z); @@ -333,7 +336,8 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, ); /// remove a block event from the block by address -extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); +extern DFHACK_EXPORT bool RemoveBlockEvent(int32_t x, int32_t y, int32_t z, df::block_square_event * which ); +extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); // todo: deprecate me DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2); DFHACK_EXPORT bool canStepBetween(df::coord pos1, df::coord pos2); diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 889a40104..06f8c9673 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -107,9 +107,9 @@ bool Maps::IsValid () } // getter for map size in blocks -void Maps::getSize (uint32_t& x, uint32_t& y, uint32_t& z) +inline void getSizeInline (int32_t& x, int32_t& y, int32_t& z) { - if (!IsValid()) + if (!Maps::IsValid()) { x = y = z = 0; return; @@ -118,14 +118,38 @@ void Maps::getSize (uint32_t& x, uint32_t& y, uint32_t& z) y = world->map.y_count_block; z = world->map.z_count_block; } +void Maps::getSize (int32_t& x, int32_t& y, int32_t& z) +{ + getSizeInline(x, y, z); +} +void Maps::getSize (uint32_t& x, uint32_t& y, uint32_t& z) //todo: deprecate me +{ + int32_t sx, sy, sz; + getSizeInline(sx, sy, sz); + x = uint32_t(sx); + y = uint32_t(sy); + z = uint32_t(sz); +} // getter for map size in tiles -void Maps::getTileSize (uint32_t& x, uint32_t& y, uint32_t& z) +inline void getTileSizeInline (int32_t& x, int32_t& y, int32_t& z) { - getSize(x, y, z); + getSizeInline(x, y, z); x *= 16; y *= 16; } +void Maps::getTileSize (int32_t& x, int32_t& y, int32_t& z) +{ + getTileSizeInline(x, y, z); +} +void Maps::getTileSize (uint32_t& x, uint32_t& y, uint32_t& z) //todo: deprecate me +{ + int32_t sx, sy, sz; + getTileSizeInline(sx, sy, sz); + x = uint32_t(sx); + y = uint32_t(sy); + z = uint32_t(sz); +} // getter for map position void Maps::getPosition (int32_t& x, int32_t& y, int32_t& z) @@ -375,12 +399,20 @@ bool GetLocalFeature(t_feature &feature, df::coord2d rgn_pos, int32_t index) return true; } -bool Maps::ReadFeatures(uint32_t x, uint32_t y, uint32_t z, t_feature *local, t_feature *global) +inline bool ReadFeaturesInline(int32_t x, int32_t y, int32_t z, t_feature *local, t_feature *global) { - df::map_block *block = getBlock(x,y,z); + df::map_block* block = Maps::getBlock(x, y, z); if (!block) return false; - return ReadFeatures(block, local, global); + return Maps::ReadFeatures(block, local, global); +} +bool Maps::ReadFeatures(int32_t x, int32_t y, int32_t z, t_feature *local, t_feature *global) +{ + return ReadFeaturesInline(x, y, z, local, global); +} +bool Maps::ReadFeatures(uint32_t x, uint32_t y, uint32_t z, t_feature *local, t_feature *global) //todo: deprecate me +{ + return ReadFeaturesInline(int32_t(x), int32_t(y), int32_t(z), local, global); } bool Maps::ReadFeatures(df::map_block * block, t_feature * local, t_feature * global) @@ -477,12 +509,11 @@ bool Maps::SortBlockEvents(df::map_block *block, return true; } -bool Maps::RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which) +inline bool RemoveBlockEventInline(int32_t x, int32_t y, int32_t z, df::block_square_event * which) { - df::map_block * block = getBlock(x,y,z); + df::map_block* block = Maps::getBlock(x, y, z); if (!block) return false; - int idx = linear_index(block->block_events, which); if (idx >= 0) { @@ -493,6 +524,14 @@ bool Maps::RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square else return false; } +inline bool Maps::RemoveBlockEvent(int32_t x, int32_t y, int32_t z, df::block_square_event * which) +{ + return RemoveBlockEventInline(x, y, z, which); +} +bool Maps::RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which) //todo: deprecate me +{ + return RemoveBlockEventInline(int32_t(x), int32_t(y), int32_t(z), which); +} static df::coord2d biome_offsets[9] = { df::coord2d(-1,-1), df::coord2d(0,-1), df::coord2d(1,-1), @@ -658,8 +697,8 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2) if ( x == 0 && y == 0 ) continue; df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z)); - df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type); - if ( shape1 == tiletype_shape::WALL ) { + df::tiletype_shape shape3 = ENUM_ATTR(tiletype,shape,*type); + if ( shape3 == tiletype_shape::WALL ) { foundWall = true; x = 2; break; @@ -695,8 +734,8 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2) if ( x == 0 && y == 0 ) continue; df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z)); - df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type); - if ( shape1 == tiletype_shape::WALL ) { + df::tiletype_shape shape3 = ENUM_ATTR(tiletype,shape,*type); + if ( shape3 == tiletype_shape::WALL ) { foundWall = true; x = 2; break; From 2aa28d34b6f96cc62412b7524807ae14666f475e Mon Sep 17 00:00:00 2001 From: Guilherme Abraham Date: Fri, 22 Apr 2022 00:22:05 -0300 Subject: [PATCH 003/854] Adding ids output to cursecheck (#2093) Co-authored-by: Guilherme Abraham Co-authored-by: Myk --- docs/Plugins.rst | 1 + docs/changelog.txt | 1 + plugins/cursecheck.cpp | 9 +++++++++ 3 files changed, 11 insertions(+) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 48ecfe453..bad1629f7 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -313,6 +313,7 @@ Options: :detail: Print full name, date of birth, date of curse and some status info (some vampires might use fake identities in-game, though). +:ids: Print the creature and race IDs. :nick: Set the type of curse as nickname (does not always show up in-game, some vamps don't like nicknames). :all: Include dead and passive cursed creatures (can result in a quite diff --git a/docs/changelog.txt b/docs/changelog.txt index 26329f0d1..330f6e8b2 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -57,6 +57,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `autochop`: preferably designate larger trees over smaller ones - `blueprint`: ``track`` phase renamed to ``carve``. carved fortifications and (optionally) engravings are now captured in blueprints - `tweak` stable-cursor: Keep the cursor stable even when the viewport moves a small amount +- `cursecheck`: Added a new parameter, ``ids``, to print creature and race IDs of the cursed creature. ## Documentation - Add more examples to the plugin skeleton files so they are more informative for a newbie diff --git a/plugins/cursecheck.cpp b/plugins/cursecheck.cpp index 69cfbc275..d20c645ab 100644 --- a/plugins/cursecheck.cpp +++ b/plugins/cursecheck.cpp @@ -146,6 +146,7 @@ command_result cursecheck (color_ostream &out, vector & parameters) Gui::getCursorCoords(cursorX,cursorY,cursorZ); bool giveDetails = false; + bool giveUnitID = false; bool giveNick = false; bool ignoreDead = true; bool verbose = false; @@ -162,6 +163,7 @@ command_result cursecheck (color_ostream &out, vector & parameters) " By default dead and passive creatures (aka really dead) are ignored.\n" "Options:\n" " detail - show details (name and age shown ingame might differ)\n" + " ids - add creature and race IDs to be show on the output\n" " nick - try to set cursetype as nickname (does not always work)\n" " all - include dead and passive creatures\n" " verbose - show all curse tags (if you really want to know it all)\n" @@ -170,6 +172,8 @@ command_result cursecheck (color_ostream &out, vector & parameters) } if(parameters[i] == "detail") giveDetails = true; + if(parameters[i] == "ids") + giveUnitID = true; if(parameters[i] == "nick") giveNick = true; if(parameters[i] == "all") @@ -269,6 +273,11 @@ command_result cursecheck (color_ostream &out, vector & parameters) << bitfield_to_string(unit->curse.add_tags2) << endl; } } + + if (giveUnitID) + { + out.print("Creature %d, race %d (%x)\n", unit->id, unit->race, unit->race); + } } } From 809f9316fdf24b42dbdf103980fee69f7b91bf5e Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Apr 2022 08:30:53 -0700 Subject: [PATCH 004/854] make frame inset configurable for ListBox list --- library/lua/gui/dialogs.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 5d2a35197..687c7176e 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -158,6 +158,7 @@ ListBox.ATTRS{ on_select2 = DEFAULT_NIL, select2_hint = DEFAULT_NIL, row_height = DEFAULT_NIL, + list_frame_inset = DEFAULT_NIL, } function ListBox:preinit(info) @@ -198,7 +199,8 @@ function ListBox:init(info) if cb then cb(obj, sel) end end, on_submit2 = on_submit2, - frame = { l = 0, r = 0 }, + frame = { l = 0, r = 0}, + frame_inset = self.list_frame_inset, row_height = info.row_height, } } From ed5523152cdad2070e8726de116665cd3380cb9c Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Apr 2022 09:53:10 -0700 Subject: [PATCH 005/854] factor text wrapping out of TooltipLabel into WrappedLabel --- docs/Lua API.rst | 41 ++++++++++++++++++++---------- docs/changelog.txt | 3 ++- library/lua/gui/widgets.lua | 50 ++++++++++++++++++++++++------------- 3 files changed, 62 insertions(+), 32 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 64b6dbf0d..a0764df99 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3864,28 +3864,43 @@ The Label widget implements the following methods: Computes the width of the text. -TooltipLabel class +WrappedLabel class ------------------ This Label subclass represents text that you want to be able to dynamically -hide, like help text in a tooltip. +wrap. This frees you from having to pre-split long strings into multiple lines +in the Label ``text`` list. + +It has the following attributes: + +:text_to_wrap: The string (or a table of strings or a function that returns a + string or a table of strings) to display. The text will be autowrapped to + the width of the widget, though any existing newlines will be kept. +:indent: The number of spaces to indent the text from the left margin. The + default is ``0``. + +The displayed text is refreshed and rewrapped whenever the widget bounds change. +To force a refresh (to pick up changes in the string that ``text_to_wrap`` +returns, for example), all ``updateLayout()`` on this widget or on a widget that +contains this widget. + +TooltipLabel class +------------------ + +This WrappedLabel subclass represents text that you want to be able to +dynamically hide, like help text in a tooltip. It has the following attributes: -:tooltip: The string (or a table of strings or a function that returns a string - or a table of strings) to display. The text will be autowrapped to the - width of the widget, though any existing newlines will be kept. -:show_tooltip: Boolean or a callback; if true, the widget is visible. Defaults - to ``true``. -:indent: The number of spaces to indent the tooltip from the left margin. The - default is ``2``. +:show_tooltip: Boolean or a callback; if true, the widget is visible. The ``text_pen`` attribute of the ``Label`` class is overridden with a default -of COLOR_GREY. +of ``COLOR_GREY`` and the ``indent`` attribute of the ``WrappedLabel`` class is +overridden with a default of ``2``. -Note that the text of the tooltip is only refreshed when the widget layout is -updated (i.e. ``updateLayout()`` is called on this widget or a widget that -contains this widget) and the tooltip needs to be rewrapped. +The text of the tooltip can be passed in the inherited ``text_to_wrap`` +attribute so it can be autowrapped, or in the basic ``text`` attribute if no +wrapping is required. HotkeyLabel class ----------------- diff --git a/docs/changelog.txt b/docs/changelog.txt index 330f6e8b2..1a3bf64b4 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -80,7 +80,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - New string class function: ``string:escape_pattern()`` escapes regex special characters within a string - ``widgets.Panel``: if ``autoarrange_subviews`` is set, ``Panel``\s will now automatically lay out widgets vertically according to their current height. This allows you to have widgets dynamically change height or become visible/hidden and you don't have to worry about recalculating frame layouts - ``widgets.ResizingPanel``: new ``Panel`` subclass that automatically recalculates it's own frame height based on the size, position, and visibility of its subviews -- ``widgets.TooltipLabel``: new ``Label`` subclass that provides tooltip-like behavior +- ``widgets.WrappedLabel``: new ``Label`` subclass that provides autowrapping of text +- ``widgets.TooltipLabel``: new ``WrappedLabel`` subclass that provides tooltip-like behavior - ``widgets.HotkeyLabel``: new ``Label`` subclass that displays and reacts to hotkeys - ``widgets.CycleHotkeyLabel``: new ``Label`` subclass that allows users to cycle through a list of options by pressing a hotkey - ``widgets.ToggleHotkeyLabel``: new ``CycleHotkeyLabel`` subclass that toggles between ``On`` and ``Off`` states diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 7a83346b1..9306a75fa 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -494,41 +494,55 @@ function Label:onInput(keys) end ------------------ --- TooltipLabel -- +-- WrappedLabel -- ------------------ -TooltipLabel = defclass(TooltipLabel, Label) +WrappedLabel = defclass(WrappedLabel, Label) -TooltipLabel.ATTRS{ - tooltip=DEFAULT_NIL, - show_tooltip=true, - indent=2, - text_pen=COLOR_GREY, +WrappedLabel.ATTRS{ + text_to_wrap=DEFAULT_NIL, + indent=0, } -function TooltipLabel:getWrappedTooltip() - local tooltip = getval(self.tooltip) - if type(tooltip) == 'table' then - tooltip = table.concat(tooltip, NEWLINE) +function WrappedLabel:getWrappedText(width) + if not self.text_to_wrap then return nil end + local text_to_wrap = getval(self.text_to_wrap) + if type(text_to_wrap) == 'table' then + text_to_wrap = table.concat(text_to_wrap, NEWLINE) end - return tooltip:wrap(self.frame_body.width - self.indent) -end - -function TooltipLabel:preUpdateLayout() - self.visible = getval(self.show_tooltip) + return text_to_wrap:wrap(width - self.indent) end -- we can't set the text in init() since we may not yet have a frame that we -- can get wrapping bounds from. -function TooltipLabel:postComputeFrame() +function WrappedLabel:postComputeFrame() + local wrapped_text = self:getWrappedText(self.frame_body.width) + if not wrapped_text then return end local text = {} - for _,line in ipairs(self:getWrappedTooltip():split(NEWLINE)) do + for _,line in ipairs(wrapped_text:split(NEWLINE)) do table.insert(text, {gap=self.indent, text=line}) + -- a trailing newline will get ignored so we don't have to manually trim table.insert(text, NEWLINE) end self:setText(text) end +------------------ +-- TooltipLabel -- +------------------ + +TooltipLabel = defclass(TooltipLabel, WrappedLabel) + +TooltipLabel.ATTRS{ + show_tooltip=DEFAULT_NIL, + indent=2, + text_pen=COLOR_GREY, +} + +function TooltipLabel:preUpdateLayout() + self.visible = getval(self.show_tooltip) +end + ----------------- -- HotkeyLabel -- ----------------- From cb18ce69cca151807f82579e3a3fe94fe9cab2f8 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 23 Apr 2022 07:17:26 +0000 Subject: [PATCH 006/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index ca963c935..d9e390cd5 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ca963c935c3375a1cf3076b066df44867edf1166 +Subproject commit d9e390cd5509458bfbc0c1e2ecb01ae96bddc015 From fe0b33d9c4e3d0da9b30fd500b45160f929661b3 Mon Sep 17 00:00:00 2001 From: Nik Nyby Date: Tue, 26 Apr 2022 10:59:51 -0400 Subject: [PATCH 007/854] fix typo: equiptment --- docs/Plugins.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index bad1629f7..b068e3c95 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -696,7 +696,7 @@ also tries to have dwarves specialize in specific skills. The key is that, for almost all labors, once a dwarf begins a job it will finish that job even if the associated labor is removed. Autolabor therefore frequently checks which dwarf or dwarves should take new jobs for that labor, and sets labors accordingly. -Labors with equiptment (mining, hunting, and woodcutting), which are abandoned +Labors with equipment (mining, hunting, and woodcutting), which are abandoned if labors change mid-job, are handled slightly differently to minimise churn. .. warning:: From dc2a14c0c2d05ba1be2bc823312a0c875dfe523a Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 26 Apr 2022 20:52:24 -0600 Subject: [PATCH 008/854] Add "partial-items" tweak. When active, the displayed names of partially-consumed items (e.g. hospital cloth) will display a percentage indicator at the end. Also re-sort a few Tweaks so they're in alphabetical order again. --- docs/Plugins.rst | 1 + docs/changelog.txt | 3 +++ plugins/tweak/tweak.cpp | 24 ++++++++++++++++++++---- plugins/tweak/tweaks/partial-items.h | 23 +++++++++++++++++++++++ 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 plugins/tweak/tweaks/partial-items.h diff --git a/docs/Plugins.rst b/docs/Plugins.rst index b068e3c95..3b42bc16a 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -159,6 +159,7 @@ Subcommands that persist until disabled or DF quits: i.e. stop the rightmost list of the Positions page of the military screen from constantly resetting to the top. :nestbox-color: Fixes the color of built nestboxes +:partial-items: Displays percentages on partially-consumed items such as hospital cloth :reaction-gloves: Fixes reactions to produce gloves in sets with correct handedness (:bug:`6273`) :shift-8-scroll: Gives Shift-8 (or :kbd:`*`) priority when scrolling menus, instead of scrolling the map :stable-cursor: Saves the exact cursor position between t/q/k/d/b/etc menus of fortress mode, if the diff --git a/docs/changelog.txt b/docs/changelog.txt index 1a3bf64b4..05fe533ba 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -38,6 +38,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Removed +## New Tweaks +- `tweak` partial-items: displays percentages on partially-consumed items such as hospital cloth + ## Fixes - `autofarm` removed restriction on only planting 'discovered' plants - `luasocket` (and others): return correct status code when closing socket connections diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 1ea958f5b..38ece6970 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -49,11 +49,14 @@ #include "df/item_glovesst.h" #include "df/item_shoesst.h" #include "df/item_pantsst.h" +#include "df/item_drinkst.h" +#include "df/item_globst.h" #include "df/item_liquid_miscst.h" #include "df/item_powder_miscst.h" #include "df/item_barst.h" #include "df/item_threadst.h" #include "df/item_clothst.h" +#include "df/item_sheetst.h" #include "df/spatter.h" #include "df/layer_object.h" #include "df/reaction.h" @@ -68,6 +71,7 @@ #include "df/job.h" #include "df/general_ref_building_holderst.h" #include "df/unit_health_info.h" +#include "df/caste_body_info.h" #include "df/activity_entry.h" #include "df/activity_event_combat_trainingst.h" #include "df/activity_event_individual_skill_drillst.h" @@ -101,14 +105,15 @@ #include "tweaks/kitchen-prefs-empty.h" #include "tweaks/max-wheelbarrow.h" #include "tweaks/military-assign.h" -#include "tweaks/pausing-fps-counter.h" #include "tweaks/nestbox-color.h" +#include "tweaks/partial-items.h" +#include "tweaks/pausing-fps-counter.h" +#include "tweaks/reaction-gloves.h" #include "tweaks/shift-8-scroll.h" #include "tweaks/stable-cursor.h" #include "tweaks/stone-status-all.h" #include "tweaks/title-start-rename.h" #include "tweaks/tradereq-pet-gender.h" -#include "tweaks/reaction-gloves.h" using std::set; using std::vector; @@ -244,6 +249,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector append(stl_sprintf(" (%i%%)", std::max(1, dimension * 100 / DIM))); \ + } \ +}; \ +IMPLEMENT_VMETHOD_INTERPOSE(partial_items_hook_##TYPE, getItemDescription); + +DEFINE_PARTIAL_ITEM_TWEAK(bar, 150) +DEFINE_PARTIAL_ITEM_TWEAK(drink, 150) +DEFINE_PARTIAL_ITEM_TWEAK(glob, 150) +DEFINE_PARTIAL_ITEM_TWEAK(liquid_misc, 150) +DEFINE_PARTIAL_ITEM_TWEAK(powder_misc, 150) +DEFINE_PARTIAL_ITEM_TWEAK(cloth, 10000) +DEFINE_PARTIAL_ITEM_TWEAK(sheet, 10000) +DEFINE_PARTIAL_ITEM_TWEAK(thread, 15000) From 6ad362d6987673f3e08097c864f18e54a19e9366 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 27 Apr 2022 11:51:46 -0700 Subject: [PATCH 009/854] return a reference to the created dialogs --- docs/changelog.txt | 1 + library/lua/gui/dialogs.lua | 26 +++++++++++++++++--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 05fe533ba..9b449f4c9 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -90,6 +90,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``widgets.ToggleHotkeyLabel``: new ``CycleHotkeyLabel`` subclass that toggles between ``On`` and ``Off`` states - ``safe_index`` now properly handles lua sparse tables that are indexed by numbers - ``widgets``: unset values in ``frame_inset``-table default to ``0`` +- ``dialogs``: ``show*`` functions now return a reference to the created dialog # 0.47.05-r4 diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 687c7176e..51f346bbd 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -69,22 +69,26 @@ function MessageBox:onInput(keys) end function showMessage(title, text, tcolor, on_close) - MessageBox{ + local mb = MessageBox{ frame_title = title, text = text, text_pen = tcolor, on_close = on_close - }:show() + } + mb:show() + return mb end function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel) - MessageBox{ + local mb = MessageBox{ frame_title = title, text = text, text_pen = tcolor, on_accept = on_accept, on_cancel = on_cancel, - }:show() + } + mb:show() + return mb end InputBox = defclass(InputBox, MessageBox) @@ -133,7 +137,7 @@ function InputBox:onInput(keys) end function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width) - InputBox{ + local ib = InputBox{ frame_title = title, text = text, text_pen = tcolor, @@ -141,7 +145,9 @@ function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_wi on_input = on_input, on_cancel = on_cancel, frame_width = min_width, - }:show() + } + ib:show() + return ib end ListBox = defclass(ListBox, MessageBox) @@ -201,7 +207,7 @@ function ListBox:init(info) on_submit2 = on_submit2, frame = { l = 0, r = 0}, frame_inset = self.list_frame_inset, - row_height = info.row_height, + row_height = self.row_height, } } end @@ -232,7 +238,7 @@ function ListBox:onInput(keys) end function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width, filter) - ListBox{ + local lb = ListBox{ frame_title = title, text = text, text_pen = tcolor, @@ -241,7 +247,9 @@ function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_ on_cancel = on_cancel, frame_width = min_width, with_filter = filter, - }:show() + } + lb:show() + return lb end return _ENV From dcadde38d77c5c9a77cf15eca2f2a90e8e764277 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 27 Apr 2022 12:16:53 -0700 Subject: [PATCH 010/854] add new global function: ensure_key --- docs/Lua API.rst | 6 ++++++ docs/changelog.txt | 1 + library/lua/dfhack.lua | 7 +++++++ 3 files changed, 14 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index a0764df99..acc9174b1 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -2643,6 +2643,12 @@ environment by the mandatory init file dfhack.lua: Walks a sequence of dereferences, which may be represented by numbers or strings. Returns *nil* if any of obj or indices is *nil*, or a numeric index is out of array bounds. +* ``ensure_key(t, key[, default_value])`` + + If the Lua table ``t`` doesn't include the specified ``key``, ``t[key]`` is + set to the value of ``default_value``, which defaults to ``{}`` if not set. + The new or existing value of ``t[key]`` is then returned. + .. _lua-string: String class extentions diff --git a/docs/changelog.txt b/docs/changelog.txt index 9b449f4c9..adc25327a 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -91,6 +91,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``safe_index`` now properly handles lua sparse tables that are indexed by numbers - ``widgets``: unset values in ``frame_inset``-table default to ``0`` - ``dialogs``: ``show*`` functions now return a reference to the created dialog +- ``ensure_key``: new global function for retrieving or dynamically creating Lua table mappings # 0.47.05-r4 diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 26e20f748..671029015 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -385,6 +385,13 @@ function safe_index(obj,idx,...) end end +function ensure_key(t, key, default_value) + if t[key] == nil then + t[key] = (default_value ~= nil) and default_value or {} + end + return t[key] +end + -- String class extentions -- prefix is a literal string, not a pattern From 8eb2831b7ed1b3062f9fde1b541da55b89bbf6ac Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 27 Apr 2022 19:53:05 -0700 Subject: [PATCH 011/854] Adds plugins/external/ and auto-populates the cmake within upon creation (#2095) * Adds a plugins sub-directory * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Auto-adds plugins/external sub-directories * Moves plugins/external globbing as to generate plugins/external/cmake * Removes plugins/external/.gitignore since the directory is generated * Fixes cmake error * Moves gitignore for plugins/external to ensure existence for fresh clone * Adds missing EOF newline * Adds requested changes --- .gitignore | 1 - plugins/CMakeLists.txt | 20 ++++++++++++++++++-- plugins/external/.gitignore | 2 ++ 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 plugins/external/.gitignore diff --git a/.gitignore b/.gitignore index 3867b9dbf..8e401a7df 100644 --- a/.gitignore +++ b/.gitignore @@ -72,5 +72,4 @@ tags .idea # external plugins -/plugins/external/ /plugins/CMakeLists.custom.txt diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 22983d617..b8cd25860 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -189,6 +189,17 @@ if(BUILD_SKELETON) add_subdirectory(skeleton) endif() +macro(subdirlist result subdir) + file(GLOB children ABSOLUTE ${subdir}/ ${subdir}/*/) + set(dirlist "") + foreach(child ${children}) + if(IS_DIRECTORY ${child}) + file(RELATIVE_PATH child ${CMAKE_CURRENT_SOURCE_DIR}/${subdir} ${child}) + list(APPEND dirlist ${child}) + endif() + endforeach() + set(${result} ${dirlist}) +endmacro() # To add "external" plugins without committing them to the DFHack repo: # @@ -204,7 +215,7 @@ endif() # 4. build DFHack as normal. The plugins you added will be built as well. if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/CMakeLists.txt") - file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/external/CMakeLists.txt" + set(content_str "# Add external plugins here - this file is ignored by git # Recommended: use add_subdirectory() for folders that you have created within @@ -212,9 +223,14 @@ if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/CMakeLists.txt") # See the end of /plugins/CMakeLists.txt for more details. ") + subdirlist(SUBDIRS external) + foreach(subdir ${SUBDIRS}) + set(content_str "${content_str}add_subdirectory(${subdir})\n") + endforeach() + file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/external/CMakeLists.txt" ${content_str}) endif() -include("${CMAKE_CURRENT_SOURCE_DIR}/external/CMakeLists.txt") +add_subdirectory(external) # for backwards compatibility if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.custom.txt") diff --git a/plugins/external/.gitignore b/plugins/external/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/plugins/external/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From f08a268e8a8e4c7d6e3299be1cf70f2b8f951d86 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Fri, 29 Apr 2022 15:55:08 +0200 Subject: [PATCH 012/854] add scroll icons to Label widget (#2101) * WIP: add scroll icons to Label widget It's an opt-out. The icons are rendered in the right-most column of the 1st and last row. They are only rendered when text can actually be scrolled in the corresponding direction. WIP: Currently, the icons might overlay text characters, there is no mechanism preventing it * gui.lua: expose the `parse_inset()` function * refactor Label's scroll icon code * since `render_scroll_icons` only works with a label, it's now a class function * `update_scroll_inset` ensures `frame_inset.r` or `.l` is at least 1, according to `show_scroll_icons` * `show_scroll_icons` has 4 possible values: `false` for no icons, `left` for icons on the first column on the left (also ensuring `frame_inset.l >= 1`), `right` - last column on the right, `DEFAULT_NIL` - same as `right` if text height greater than `frame_body.height`, else same as `false`. * make `render_scroll_icons` always draw icons The check now happens in `onRenderFrame` * draw frame's background calling `Label.super.onRenderFrame(self, dc, rect)` makes frame's background invisible for some reason * remove trailing spaces * fix scroll icons placed far above/below text With `Label.frame_inset = 1` the text could be vertically centered with plenty of space below and above, but not all rendered. Before this change, the scroll icons would be at the very top and bottom of the frame instead of near the first and last rendered text line. * always `update_scroll_inset` to react to resized window * draw scroll icons next to text * update `Lua API.rst` with new `Label` parameters * move comment separator up This way every scroll related parameter is in one group * list default values for new parameters in docs * add missing description of `Label.scroll_keys` --- docs/Lua API.rst | 8 ++++++ library/lua/gui.lua | 2 +- library/lua/gui/widgets.lua | 53 +++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index acc9174b1..a61264c86 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3782,6 +3782,14 @@ It has the following attributes: :auto_width: Sets self.frame.w from the text width. :on_click: A callback called when the label is clicked (optional) :on_rclick: A callback called when the label is right-clicked (optional) +:scroll_keys: Specifies which keys the label should react to as a table. Default is ``STANDARDSCROLL`` (up or down arrows, page up or down). +:show_scroll_icons: Controls scroll icons' behaviour: ``false`` for no icons, ``'right'`` or ``'left'`` for + icons next to the text in an additional column (``frame_inset`` is adjusted to have ``.r`` or ``.l`` greater than ``0``), + ``nil`` same as ``'right'`` but changes ``frame_inset`` only if a scroll icon is actually necessary + (if ``getTextHeight()`` is greater than ``frame_body.height``). Default is ``nil``. +:up_arrow_icon: The symbol for scroll up arrow. Default is ``string.char(24)`` (``↑``). +:down_arrow_icon: The symbol for scroll down arrow. Default is ``string.char(25)`` (``↓``). +:scroll_icon_pen: Specifies the pen for scroll icons. Default is ``COLOR_LIGHTCYAN``. The text itself is represented as a complex structure, and passed to the object via the ``text`` argument of the constructor, or via diff --git a/library/lua/gui.lua b/library/lua/gui.lua index a4541a6d8..8521e1dfe 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -96,7 +96,7 @@ function compute_frame_rect(wavail,havail,spec,xgap,ygap) return rect end -local function parse_inset(inset) +function parse_inset(inset) local l,r,t,b if type(inset) == 'table' then l,r = inset.l or inset.x or 0, inset.r or inset.x or 0 diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 9306a75fa..8a091804c 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -412,7 +412,12 @@ Label.ATTRS{ auto_width = false, on_click = DEFAULT_NIL, on_rclick = DEFAULT_NIL, + -- scroll_keys = STANDARDSCROLL, + show_scroll_icons = DEFAULT_NIL, -- DEFAULT_NIL, 'right', 'left', false + up_arrow_icon = string.char(24), + down_arrow_icon = string.char(25), + scroll_icon_pen = COLOR_LIGHTCYAN, } function Label:init(args) @@ -435,6 +440,39 @@ function Label:setText(text) end end +function Label:update_scroll_inset() + if self.show_scroll_icons == nil then + self._show_scroll_icons = self:getTextHeight() > self.frame_body.height and 'right' or false + else + self._show_scroll_icons = self.show_scroll_icons + end + if self._show_scroll_icons then + -- here self._show_scroll_icons can only be either + -- 'left' or any true value which we interpret as right + local l,t,r,b = gui.parse_inset(self.frame_inset) + if self._show_scroll_icons == 'left' and l <= 0 then + l = 1 + elseif r <= 0 then + r = 1 + end + self.frame_inset = {l=l,t=t,r=r,b=b} + end +end + +function Label:render_scroll_icons(dc, x, y1, y2) + if self.start_line_num ~= 1 then + dc:seek(x, y1):char(self.up_arrow_icon, self.scroll_icon_pen) + end + local last_visible_line = self.start_line_num + self.frame_body.height - 1 + if last_visible_line < self:getTextHeight() then + dc:seek(x, y2):char(self.down_arrow_icon, self.scroll_icon_pen) + end +end + +function Label:postComputeFrame() + self:update_scroll_inset() +end + function Label:preUpdateLayout() if self.auto_width then self.frame = self.frame or {} @@ -465,6 +503,21 @@ function Label:onRenderBody(dc) render_text(self,dc,0,0,text_pen,self.text_dpen,is_disabled(self)) end +function Label:onRenderFrame(dc, rect) + if self._show_scroll_icons + and self:getTextHeight() > self.frame_body.height + then + local x = self._show_scroll_icons == 'left' + and self.frame_body.x1-dc.x1-1 + or self.frame_body.x2-dc.x1+1 + self:render_scroll_icons(dc, + x, + self.frame_body.y1-dc.y1, + self.frame_body.y2-dc.y1 + ) + end +end + function Label:scroll(nlines) local n = self.start_line_num + nlines n = math.min(n, self:getTextHeight() - self.frame_body.height + 1) From b9c36c1e632571a2257a071e2c2843959ae2ed0b Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Fri, 29 Apr 2022 16:39:59 +0100 Subject: [PATCH 013/854] Added custom-raw-tokens utility (#2038) * Added (chain) for [CHAIN_METAL_TEXT] armours in gui/materials.lua used by gui/create-item-- again (oops) * Added customRawData utility * Oops, whitespace * Revised rawStringsFieldNames * Dialed down on lua trickery and fixed wrongly formatted changelog entry * Fixed changelog in wrong place and made customRawData a proper module * Fixed not caching not-present tags, revised examples and fixed error * Fixed whitespace. Changing settings in editor! * customRawData docs * Added getCreatureTag for respecting caste tags, "fixed" bizarre caching error (quotes because I don't even know what was causing it) and updated docs * Added line limiting for docs, I guess * Added missing string convert argument * docs indent fix, code block fix, and revision * Major revision * gdi, docs error * Another? But... huh. * ... * Made requested changes * Whoops * Rearrange docs lines * Followed example, should fix linter issues * fix typo. linted offline this time...... * Make it so that last instance of tag is what is read from * Added requested change * eventful key change * i to lenArgs * change eventful key * add test for broken caste selection * Major redesign * tags --> tokens * Added plant growth behaviour and did some requested changes * More error handling * fix docs * Added basic error suppression * Docs clarification. * Docs registering example and fix error * Strip errors on frame after onWorldLoad, not on map load * Revert "Strip errors on frame after onWorldLoad, not on map load" This reverts commit e20a0ef8d3743f79d961077f46910b77b16f36b9. * Revert "Docs registering example and fix error" This reverts commit 9c848c54c3f84e0ecc1dc421137c8a8b4a52280d. * Revert "Docs clarification." This reverts commit 6b4b6a1aa40c50398504f37ecf1ff0f93d6459b1. * Revert "Added basic error suppression" This reverts commit d11cb1438cf1e56ff700469e944f0b9af64651d7. * Use more eventful key more consistent with other files * use onStateChange instead of eventful and remove redundant utils require * Code review stuff * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update docs/Lua API.rst committing a suggestion Co-authored-by: Alan * Prepend examples with DFHACK_ * Remove unused parameters * Use new ensure_key global * Named a couple of unnamed arguments (untested) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Alan --- docs/Lua API.rst | 74 ++++++ docs/changelog.txt | 1 + library/lua/custom-raw-tokens.lua | 361 ++++++++++++++++++++++++++++++ 3 files changed, 436 insertions(+) create mode 100644 library/lua/custom-raw-tokens.lua diff --git a/docs/Lua API.rst b/docs/Lua API.rst index a61264c86..4615aaf4f 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3198,6 +3198,80 @@ Predefined instance methods: To avoid confusion, these methods cannot be redefined. +.. _custom-raw-tokens: + +custom-raw-tokens +================= + +A module for reading custom tokens added to the raws by mods. + +* ``customRawTokens.getToken(typeDefinition, token)`` + + Where ``typeDefinition`` is a type definition struct as seen in ``df.global.world.raws`` + (e.g.: ``dfhack.gui.getSelectedItem().subtype``) and ``token`` is the name of the custom token + you want read. The arguments from the token will then be returned as strings using single or + multiple return values. If the token is not present, the result is false; if it is present + but has no arguments, the result is true. For ``creature_raw``, it checks against no caste. + For ``plant_raw``, it checks against no growth. + +* ``customRawTokens.getToken(typeInstance, token)`` + + Where ``typeInstance`` is a unit, entity, item, job, projectile, building, plant, or interaction + instance. Gets ``typeDefinition`` and then returns the same as ``getToken(typeDefinition, token)``. + For units, it gets the token from the race or caste instead if appplicable. For plants growth items, + it gets the token from the plant or plant growth instead if applicable. For plants it does the same + but with growth number -1. + +* ``customRawTokens.getToken(raceDefinition, casteNumber, token)`` + + The same as ``getToken(unit, token)`` but with a specified race and caste. Caste number -1 is no caste. + +* ``customRawTokens.getToken(raceDefinition, casteName, token)`` + + The same as ``getToken(unit, token)`` but with a specified race and caste, using caste name (e.g. "FEMALE") + instead of number. + +* ``customRawTokens.getToken(plantDefinition, growthNumber, token)`` + + The same as ``getToken(plantGrowthItem, token)`` but with a specified plant and growth. Growth number -1 + is no growth. + +* ``customRawTokens.getToken(plantDefinition, growthName, token)`` + + The same as ``getToken(plantGrowthItem, token)`` but with a specified plant and growth, using growth name + (e.g. "LEAVES") instead of number. + +Examples: + +* Using an eventful onReactionComplete hook, something for disturbing dwarven science:: + + if customRawTokens.getToken(reaction, "DFHACK_CAUSES_INSANITY") then + -- make unit who performed reaction go insane + +* Using an eventful onProjItemCheckMovement hook, a fast or slow-firing crossbow:: + + -- check projectile distance flown is zero, get firer, etc... + local multiplier = tonumber(customRawTokens.getToken(bow, "DFHACK_FIRE_RATE_MULTIPLIER")) or 1 + firer.counters.think_counter = firer.counters.think_counter * multiplier + +* Something for a script that prints help text about different types of units:: + + local unit = dfhack.gui.getSelectedUnit() + if not unit then return end + local helpText = customRawTokens.getToken(unit, "DFHACK_HELP_TEXT") + if helpText then print(helpText) end + +* Healing armour:: + + -- (per unit every tick) + local healAmount = 0 + for _, entry in ipairs(unit.inventory) do + if entry.mode == 2 then -- Worn + healAmount = healAmount + tonumber((customRawTokens.getToken(entry.item, "DFHACK_HEAL_AMOUNT")) or 0) + end + end + unit.body.blood_count = math.min(unit.body.blood_max, unit.body.blood_count + healAmount) + ================== In-game UI Library ================== diff --git a/docs/changelog.txt b/docs/changelog.txt index adc25327a..8495f6717 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -78,6 +78,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``widgets.FilteredList`` now allows all punctuation to be typed into the filter and can match search keys that start with punctuation. - ``widgets.ListBox``: minimum height of dialog is now calculated correctly when there are no items in the list (e.g. when a filter doesn't match anything) - Lua wrappers for functions reverse-engineered from ambushing unit code: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` +- Added `custom-raw-tokens` utility to Lua library for reading tokens added to raws by mods. - ``dwarfmode.MenuOverlay``: if ``sidebar_mode`` attribute is set, automatically manage entering a specific sidebar mode on show and restoring the previous sidebar mode on dismiss - ``dwarfmode.enterSidebarMode()``: passing ``df.ui_sidebar_mode.DesignateMine`` now always results in you entering ``DesignateMine`` mode and not ``DesignateChopTrees``, even when you looking at the surface where the default designation mode is ``DesignateChopTrees`` - New string class function: ``string:escape_pattern()`` escapes regex special characters within a string diff --git a/library/lua/custom-raw-tokens.lua b/library/lua/custom-raw-tokens.lua new file mode 100644 index 000000000..6c19deb72 --- /dev/null +++ b/library/lua/custom-raw-tokens.lua @@ -0,0 +1,361 @@ +--[[ +custom-raw-tokens +Allows for reading custom tokens added to raws by mods +by Tachytaenius (wolfboyft) + +Yes, non-vanilla raw tokens do quietly print errors into the error log but the error log gets filled with garbage anyway + +NOTE: This treats plant growths similarly to creature castes but there is no way to deselect a growth, so don't put a token you want to apply to a whole plant after any growth definitions +]] + +local _ENV = mkmodule("custom-raw-tokens") + +local customRawTokensCache = {} +dfhack.onStateChange.customRawTokens = function(code) + if code == SC_WORLD_UNLOADED then + customRawTokensCache = {} + end +end + +local function doToken(cacheTable, token, iter) + local args, lenArgs = {}, 0 + for arg in iter do + lenArgs = lenArgs + 1 + args[lenArgs] = arg + end + if lenArgs == 0 then + cacheTable[token] = true + return true + else + cacheTable[token] = args + return table.unpack(args) + end +end + +local function getSubtype(item) + if item:getSubtype() == -1 then return nil end -- number + return dfhack.items.getSubtypeDef(item:getType(), item:getSubtype()) -- struct +end + +local rawStringsFieldNames = { + [df.inorganic_raw] = "str", + [df.plant_raw] = "raws", + [df.creature_raw] = "raws", + [df.itemdef_weaponst] = "raw_strings", + [df.itemdef_trapcompst] = "raw_strings", + [df.itemdef_toyst] = "raw_strings", + [df.itemdef_toolst] = "raw_strings", + [df.itemdef_instrumentst] = "raw_strings", + [df.itemdef_armorst] = "raw_strings", + [df.itemdef_ammost] = "raw_strings", + [df.itemdef_siegeammost] = "raw_strings", + [df.itemdef_glovesst] = "raw_strings", + [df.itemdef_shoesst] = "raw_strings", + [df.itemdef_shieldst] = "raw_strings", + [df.itemdef_helmst] = "raw_strings", + [df.itemdef_pantsst] = "raw_strings", + [df.itemdef_foodst] = "raw_strings", + [df.entity_raw] = "raws", + [df.language_word] = "str", + [df.language_symbol] = "str", + [df.language_translation] = "str", + [df.reaction] = "raw_strings", + [df.interaction] = "str" +} + +local function getTokenCore(typeDefinition, token) + -- Have we got a table for this item subtype/reaction/whatever? + -- tostring is needed here because the same raceDefinition key won't give the same value every time + local thisTypeDefCache = ensure_key(customRawTokensCache, tostring(typeDefinition)) + + -- Have we already extracted and stored this custom raw token for this type definition? + local tokenData = thisTypeDefCache[token] + if tokenData ~= nil then + if type(tokenData) == "table" then + return table.unpack(tokenData) + else + return tokenData + end + end + + -- Get data anew + local success, dftype = pcall(function() return typeDefinition._type end) + local rawStrings = typeDefinition[rawStringsFieldNames[dftype]] + if not success or not rawStrings then + error("Expected a raw type definition or instance in argument 1") + end + local currentTokenIterator + for _, rawString in ipairs(rawStrings) do -- e.g. "[CUSTOM_TOKEN:FOO:2]" + local noBrackets = rawString.value:sub(2, -2) + local iter = noBrackets:gmatch("[^:]*") -- iterate over all the text between colons between the brackets + if token == iter() then + currentTokenIterator = iter -- we return for last instance of token if multiple instances are present + end + end + if currentTokenIterator then + return doToken(thisTypeDefCache, token, currentTokenIterator) + end + -- Not present + thisTypeDefCache[token] = false + return false +end + +local function getRaceCasteTokenCore(raceDefinition, casteNumber, token) + -- Have we got tables for this race/caste pair? + local thisRaceDefCache = ensure_key(customRawTokensCache, tostring(raceDefinition)) + local thisRaceDefCacheCaste = ensure_key(thisRaceDefCache, casteNumber) + + -- Have we already extracted and stored this custom raw token for this race/caste pair? + local tokenData = thisRaceDefCacheCaste[token] + if tokenData ~= nil then + if type(tokenData) == "table" then + return table.unpack(tokenData) + elseif tokenData == false and casteNumber ~= -1 then + return getRaceCasteTokenCore(raceDefinition, -1, token) + else + return tokenData + end + end + + -- Get data anew. Here we have to track what caste is currently being written to + local casteId, thisCasteActive + if casteNumber ~= -1 then + casteId = raceDefinition.caste[casteNumber].caste_id + thisCasteActive = false + else + thisCasteActive = true + end + local currentTokenIterator + for _, rawString in ipairs(raceDefinition.raws) do + local noBrackets = rawString.value:sub(2, -2) + local iter = noBrackets:gmatch("[^:]*") + local rawStringToken = iter() + if rawStringToken == "CASTE" or rawStringToken == "SELECT_CASTE" or rawStringToken == "SELECT_ADDITIONAL_CASTE" or rawStringToken == "USE_CASTE" then + local newCaste = iter() + if newCaste then + thisCasteActive = newCaste == casteId or rawStringToken == "SELECT_CASTE" and newCaste == "ALL" + end + elseif thisCasteActive and token == rawStringToken then + currentTokenIterator = iter + end + end + if currentTokenIterator then + return doToken(thisRaceDefCache, token, currentTokenIterator) + end + thisRaceDefCacheCaste[token] = false + if casteNumber == -1 then + return false -- Don't get into an infinite loop! + end + -- Not present, try with no caste + return getRaceCasteTokenCore(raceDefinition, -1, token) +end + +local function getPlantGrowthTokenCore(plantDefinition, growthNumber, token) + -- Have we got tables for this plant/growth pair? + local thisPlantDefCache = ensure_key(customRawTokensCache, tostring(plantDefinition)) + local thisPlantDefCacheGrowth = ensure_key(thisPlantDefCache, growthNumber) + + -- Have we already extracted and stored this custom raw token for this plant/growth pair? + local tokenData = thisPlantDefCacheGrowth[token] + if tokenData ~= nil then + if type(tokenData) == "table" then + return table.unpack(tokenData) + elseif tokenData == false and growthNumber ~= -1 then + return getPlantGrowthTokenCore(plantDefinition, -1, token) + else + return tokenData + end + end + + -- Get data anew. Here we have to track what growth is currently being written to + local growthId, thisGrowthActive + if growthNumber ~= -1 then + growthId = plantDefinition.growths[growthNumber].id + thisGrowthActive = false + else + thisGrowthActive = true + end + local currentTokenIterator + for _, rawString in ipairs(plantDefinition.raws) do + local noBrackets = rawString.value:sub(2, -2) + local iter = noBrackets:gmatch("[^:]*") + local rawStringToken = iter() + if rawStringToken == "GROWTH" then + local newGrowth = iter() + if newGrowth then + thisGrowthActive = newGrowth == growthId + end + elseif thisGrowthActive and token == rawStringToken then + currentTokenIterator = iter + end + end + if currentTokenIterator then + return doToken(thisPlantDefCache, token, currentTokenIterator) + end + thisPlantDefCacheGrowth[token] = false + if growthNumber == -1 then + return false + end + return getPlantGrowthTokenCore(plantDefinition, -1, token) +end + +--[[ +Function signatures: +getToken(rawStruct, token) +getToken(rawStructInstance, token) +getToken(raceDefinition, casteNumber, token) +getToken(raceDefinition, casteString, token) +getToken(plantDefinition, growthNumber, token) +getToken(plantDefinition, growthString, token) +]] + +local function getTokenArg1RaceDefinition(raceDefinition, b, c) + local casteNumber, token + if not c then + -- 2 arguments + casteNumber = -1 + assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string") + token = b + elseif type(b) == "number" then + -- 3 arguments, casteNumber + assert(b == -1 or b < #raceDefinition.caste and math.floor(b) == b and b >= 0, "Invalid argument 2 to getToken, must be -1 or a caste name or number present in the creature raw") + casteNumber = b + assert(type(c) == "string", "Invalid argument 3 to getToken, must be a string") + token = c + else + -- 3 arguments, casteString + assert(type(b) == "string", "Invalid argument 2 to getToken, must be -1 or a caste name or number present in the creature raw") + local casteString = b + for i, v in ipairs(raceDefinition.caste) do + if v.caste_id == casteString then + casteNumber = i + break + end + end + assert(casteNumber, "Invalid argument 2 to getToken, caste name \"" .. casteString .. "\" not found") + assert(type(c) == "string", "Invalid argument 3 to getToken, must be a string") + token = c + end + return getRaceCasteTokenCore(raceDefinition, casteNumber, token) +end + +local function getTokenArg1PlantDefinition(plantDefinition, b, c) + local growthNumber, token + if not c then + -- 2 arguments + growthNumber = -1 + assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string") + token = b + elseif type(b) == "number" then + -- 3 arguments, growthNumber + assert(b == -1 or b < #plantDefinition.growths and math.floor(b) == b and b >= 0, "Invalid argument 2 to getToken, must be -1 or a growth name or number present in the plant raw") + growthNumber = b + assert(type(c) == "string", "Invalid argument 3 to getToken, must be a string") + token = c + else + -- 3 arguments, growthString + assert(type(b) == "string", "Invalid argument 2 to getToken, must be -1 or a growth name or number present in the plant raw") + local growthString = b + for i, v in ipairs(plantDefinition.growths) do + if v.id == growthString then + growthNumber = i + break + end + end + assert(growthNumber, "Invalid argument 2 to getToken, growth name \"" .. growthString .. "\" not found") + assert(type(c) == "string", "Invalid argument 3 to getToken, must be a string") + token = c + end + return getPlantGrowthTokenCore(plantDefinition, growthNumber, token) +end + +local function getTokenArg1Else(userdata, token) + assert(type(token) == "string", "Invalid argument 2 to getToken, must be a string") + local rawStruct + if df.is_instance(df.historical_entity, userdata) then + rawStruct = userdata.entity_raw + elseif df.is_instance(df.item, userdata) then + rawStruct = getSubtype(userdata) + elseif df.is_instance(df.job, userdata) then + if job.job_type == df.job_type.CustomReaction then + for i, v in ipairs(df.global.world.raws.reactions.reactions) do + if job.reaction_name == v.code then + rawStruct = v + break + end + end + end + elseif df.is_instance(df.proj_itemst, userdata) then + if not userdata.item then return false end + if df.is_instance(df.item_plantst, userdata.item) or df.is_instance(df.item_plant_growthst, userdata.item) then + -- use plant behaviour from getToken + return getToken(userdata.item, token) + end + rawStruct = userdata.item and userdata.item.subtype + elseif df.is_instance(df.proj_unitst, userdata) then + if not usertdata.unit then return false end + -- special return so do tag here + local unit = userdata.unit + return getRaceCasteTokenCore(df.global.world.raws.creatures.all[unit.race], unit.caste, token) + elseif df.is_instance(df.building_workshopst, userdata) or df.is_instance(df.building_furnacest, userdata) then + rawStruct = df.building_def.find(userdata.custom_type) + elseif df.is_instance(df.interaction_instance, userdata) then + rawStruct = df.global.world.raws.interactions[userdata.interaction_id] + else + -- Assume raw struct *is* argument 1 + rawStruct = userdata + end + if not rawStruct then return false end + return getTokenCore(rawStruct, token) +end + +function getToken(from, b, c) + -- Argument processing + assert(from and type(from) == "userdata", "Expected userdata for argument 1 to getToken") + if df.is_instance(df.creature_raw, from) then + -- Signatures from here: + -- getToken(raceDefinition, casteNumber, token) + -- getToken(raceDefinition, casteString, token) + return getTokenArg1RaceDefinition(from, b, c) + elseif df.is_instance(df.unit, from) then + -- Signatures from here: + -- getToken(rawStructInstance, token) + assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string") + local unit, token = from, b + return getRaceCasteTokenCore(df.global.world.raws.creatures.all[unit.race], unit.caste, token) + elseif df.is_instance(df.plant_raw, from) then + -- Signatures from here: + -- getToken(plantDefinition, growthNumber, token) + -- getToken(plantDefinition, growthString, token) + return getTokenArg1PlantDefinition(from, b, c) + elseif df.is_instance(df.plant, from) then + -- Signatures from here: + -- getToken(rawStructInstance, token) + assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string") + local plantDefinition, plantGrowthNumber, token = df.global.world.raws.plants.all[from.material], -1, b + return getPlantGrowthTokenCore(plantDefinition, plantGrowthNumber, token) + elseif df.is_instance(df.item_plantst, from) then + -- Signatures from here: + -- getToken(rawStructInstance, token) + local matInfo = dfhack.matinfo.decode(from) + if matInfo.mode ~= "plant" then return false end + assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string") + local plantDefinition, plantGrowthNumber, token = matInfo.plant, -1, b + return getPlantGrowthTokenCore(plantDefinition, plantGrowthNumber, token) + elseif df.is_instance(df.item_plant_growthst, from) then + -- Signatures from here: + -- getToken(rawStructInstance, token) + local matInfo = dfhack.matinfo.decode(from) + if matInfo.mode ~= "plant" then return false end + assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string") + local plantDefinition, plantGrowthNumber, token = matInfo.plant, from.growth_print, b + return getPlantGrowthTokenCore(plantDefinition, plantGrowthNumber, token) + else + -- Signatures from here: + -- getToken(rawStruct, token) + -- getToken(rawStructInstance, token) + return getTokenArg1Else(from, b) + end +end + +return _ENV From af47434f52b2bfb1b3e85f0dbbf46c88e4bb110d Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 29 Apr 2022 11:29:00 -0700 Subject: [PATCH 014/854] protect against 0 width in string:wrap() --- library/lua/dfhack.lua | 1 + library/lua/gui/widgets.lua | 3 ++- test/library/string.lua | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 671029015..e476ffa11 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -439,6 +439,7 @@ end -- multiple lines. If width is not specified, 72 is used. function string:wrap(width) width = width or 72 + if width <= 0 then error('expected width > 0; got: '..tostring(width)) end local wrapped_text = {} for line in self:gmatch('[^\n]*') do local line_start_pos = 1 diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 8a091804c..b8782bd04 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -558,7 +558,8 @@ WrappedLabel.ATTRS{ } function WrappedLabel:getWrappedText(width) - if not self.text_to_wrap then return nil end + -- 0 width can happen if the parent has 0 width + if not self.text_to_wrap or width <= 0 then return nil end local text_to_wrap = getval(self.text_to_wrap) if type(text_to_wrap) == 'table' then text_to_wrap = table.concat(text_to_wrap, NEWLINE) diff --git a/test/library/string.lua b/test/library/string.lua index d45be4b3f..d22f262bf 100644 --- a/test/library/string.lua +++ b/test/library/string.lua @@ -67,6 +67,8 @@ function test.wrap() expect.eq('hel\nlo\nwor\nld', ('hello world'):wrap(3)) expect.eq('hel\nloo\nwor\nldo', ('helloo worldo'):wrap(3)) expect.eq('', (''):wrap()) + + expect.error_match('expected width > 0', function() ('somestr'):wrap(0) end) end function test.escape_pattern() From e603fee3ce89301a170cde9147e57795fae7d730 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 29 Apr 2022 12:10:05 -0700 Subject: [PATCH 015/854] move sidebar mode management code to init from onAboutToShow(). this allows the frames to be calculated correctly during widget instantiation. otherwise widgets can end up with -1 width --- library/lua/gui/dwarfmode.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index b79f7a9b7..15b8cd1e8 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -443,11 +443,7 @@ MenuOverlay.ATTRS { sidebar_mode = DEFAULT_NIL, } -function MenuOverlay:computeFrame(parent_rect) - return self.df_layout.menu, gui.inset_frame(self.df_layout.menu, self.frame_inset) -end - -function MenuOverlay:onAboutToShow(parent) +function MenuOverlay:init() if not dfhack.isMapLoaded() then -- sidebar menus are only valid when a fort map is loaded error('A fortress map must be loaded.') @@ -471,7 +467,13 @@ function MenuOverlay:onAboutToShow(parent) enterSidebarMode(self.sidebar_mode) end +end +function MenuOverlay:computeFrame(parent_rect) + return self.df_layout.menu, gui.inset_frame(self.df_layout.menu, self.frame_inset) +end + +function MenuOverlay:onAboutToShow(parent) self:updateLayout() if not self.df_layout.menu then error("The menu panel of dwarfmode is not visible") From 24dd4d8ac0170a5559cb409d26b09a455fc099bf Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 29 Apr 2022 20:24:09 -0700 Subject: [PATCH 016/854] Update init files (#2117) * update example init files replace onLoad.init-example with documentation on how to create scripts that run on world/map load it was confusing to show it being loaded with sc-script since it gets autorun anyway if it is just named properly * update changelog * add quickfort keybinding * move standard tweaks from dreamfort init to main --- data/examples/init/onMapLoad_dreamfort.init | 33 +++++++++++---------- dfhack.init-example | 19 +++++++----- docs/changelog.txt | 1 + library/CMakeLists.txt | 2 +- onLoad.init-example | 1 - 5 files changed, 31 insertions(+), 25 deletions(-) delete mode 100644 onLoad.init-example diff --git a/data/examples/init/onMapLoad_dreamfort.init b/data/examples/init/onMapLoad_dreamfort.init index 8e9c0134d..c75620846 100644 --- a/data/examples/init/onMapLoad_dreamfort.init +++ b/data/examples/init/onMapLoad_dreamfort.init @@ -3,29 +3,31 @@ # configuration here is useful for any fort! Feed free to edit or override # to your liking. -# Disallow cooking of otherwise useful item types -on-new-fortress ban-cooking tallow; ban-cooking honey; ban-cooking oil; ban-cooking seeds; ban-cooking brew; ban-cooking fruit; ban-cooking mill; ban-cooking thread; ban-cooking milk; ban-cooking booze - # Uncomment this next line if you want buildingplan (and quickfort) to use only -# blocks for construction. If you do uncomment, be sure to bring some blocks -# with you for starting workshops! +# blocks (not bars or logs) for constructions and workshops. If you do +# uncomment, be sure to bring some blocks with you for starting workshops! #on-new-fortress buildingplan set boulders false; buildingplan set logs false +# Disable cooking of useful item types when you start a new fortress. +on-new-fortress ban-cooking tallow; ban-cooking honey; ban-cooking oil; ban-cooking seeds; ban-cooking brew; ban-cooking fruit; ban-cooking mill; ban-cooking thread; ban-cooking milk; ban-cooking booze + +# Show a warning dialog when units are starving repeat -name warn-starving -time 10 -timeUnits days -command [ warn-starving ] -repeat -name burial -time 7 -timeUnits days -command [ burial -pets ] + +# Force dwarves to drop tattered clothing instead of clinging to the scraps repeat -name cleanowned -time 1 -timeUnits months -command [ cleanowned X ] -repeat -name clean -time 1 -timeUnits months -command [ clean all ] -repeat -name feeding-timers -time 1 -timeUnits months -command [ fix/feeding-timers ] -repeat -name stuckdoors -time 1 -timeUnits months -command [ fix/stuckdoors ] + +# Automatically enqueue orders to shear and milk animals repeat -name autoShearCreature -time 14 -timeUnits days -command [ workorder ShearCreature ] repeat -name autoMilkCreature -time 14 -timeUnits days -command [ workorder "{\"job\":\"MilkCreature\",\"item_conditions\":[{\"condition\":\"AtLeast\",\"value\":2,\"flags\":[\"empty\"],\"item_type\":\"BUCKET\"}]}" ] + +# Fulfill high-volume orders before slower once-daily orders repeat -name orders-sort -time 1 -timeUnits days -command [ orders sort ] -tweak fast-heat 100 -tweak do-job-now -fix/blood-del enable +# Don't let caravans bring barrels of blood and other useless liquids +fix/blood-del -# manages crop assignment for farm plots +# Manages crop assignment for farm plots enable autofarm autofarm default 30 autofarm threshold 150 GRASS_TAIL_PIG @@ -37,7 +39,8 @@ enable automelt enable tailor tailor enable -# auto-assigns nesting birds to nestbox zones +# auto-assigns nesting birds to nestbox zones and protects fertile eggs from +# being cooked/eaten enable zone nestboxes autonestbox start @@ -77,7 +80,7 @@ on-new-fortress autobutcher target 50 50 14 2 BIRD_GOOSE on-new-fortress autobutcher target 2 2 4 2 ALPACA SHEEP LLAMA # pigs give milk and meat and are zero-maintenance. on-new-fortress autobutcher target 5 5 6 2 PIG -# generally unprofitable animals +# butcher all unprofitable animals on-new-fortress autobutcher target 0 0 0 0 HORSE YAK DONKEY WATER_BUFFALO GOAT CAVY BIRD_DUCK BIRD_GUINEAFOWL # start it up! on-new-fortress autobutcher start; autobutcher watch all; autobutcher autowatch diff --git a/dfhack.init-example b/dfhack.init-example index 702ce5276..d181fcaa7 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -34,6 +34,9 @@ keybinding add Ctrl-K autodump-destroy-item # quicksave, only in main dwarfmode screen and menu page keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave +# gui/quickfort script - apply pre-made blueprints to the map +keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort + # gui/rename script - rename units and buildings keybinding add Ctrl-Shift-N gui/rename keybinding add Ctrl-Shift-T "gui/rename unit-profession" @@ -155,9 +158,6 @@ keybinding add Alt-W@dfhack/lua/status_overlay "gui/workflow status" # autobutcher front-end keybinding add Shift-B@pet/List/Unit "gui/autobutcher" -# assign weapon racks to squads so that they can be used -keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack - # view pathable tiles from active cursor keybinding add Alt-Shift-P@dwarfmode/LookAround gui/pathable @@ -186,9 +186,6 @@ tweak military-stable-assign # in same list, color units already assigned to squads in brown & green tweak military-color-assigned -# remove inverse dependency of squad training speed on unit list size and use more sparring -# tweak military-training - # make crafted cloth items wear out with time like in old versions (bug 6003) tweak craft-age-wear @@ -210,17 +207,22 @@ tweak hotkey-clear # Allows lowercase letters in embark profile names, and allows exiting the name prompt without saving tweak embark-profile-name +# Reduce performance impact of temperature changes +tweak fast-heat 100 + # Misc. UI tweaks tweak block-labors # Prevents labors that can't be used from being toggled tweak burrow-name-cancel tweak cage-butcher tweak civ-view-agreement +tweak do-job-now tweak eggs-fertile tweak fps-min tweak hide-priority tweak kitchen-prefs-all tweak kitchen-prefs-empty tweak max-wheelbarrow +tweak partial-items tweak shift-8-scroll tweak stone-status-all tweak title-start-rename @@ -289,5 +291,6 @@ gui/load-screen enable # Extra DFHack command files # ############################## -# Run commands in this file when a world loads -sc-script add SC_WORLD_LOADED onLoad.init-example +# Create a file named "onLoad.init" to run commands when a world is loaded +# and/or create a file named "onMapLoad.init" to run commands when a map is +# loaded. See the hack/examples/init/ directory for useful pre-made init files. diff --git a/docs/changelog.txt b/docs/changelog.txt index 8495f6717..f4d283099 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -61,6 +61,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `blueprint`: ``track`` phase renamed to ``carve``. carved fortifications and (optionally) engravings are now captured in blueprints - `tweak` stable-cursor: Keep the cursor stable even when the viewport moves a small amount - `cursecheck`: Added a new parameter, ``ids``, to print creature and race IDs of the cursed creature. +- Include recently-added tweaks in example dfhack.init file, clean up dreamfort onMapLoad.init file ## Documentation - Add more examples to the plugin skeleton files so they are more informative for a newbie diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 026558ab0..4ec165308 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -461,7 +461,7 @@ endif() install(FILES xml/symbols.xml DESTINATION ${DFHACK_DATA_DESTINATION}) # install the example autoexec file -install(FILES ../dfhack.init-example ../onLoad.init-example +install(FILES ../dfhack.init-example DESTINATION ${DFHACK_BINARY_DESTINATION}) install(TARGETS dfhack-run dfhack-client binpatch diff --git a/onLoad.init-example b/onLoad.init-example deleted file mode 100644 index 1d32f07c9..000000000 --- a/onLoad.init-example +++ /dev/null @@ -1 +0,0 @@ -repeat -name warn-starving -time 10 -timeUnits days -command [ warn-starving ] From e2fb15a3a548968a24ca180df95b9e09de49e421 Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 29 Apr 2022 20:32:22 -0700 Subject: [PATCH 017/854] add dwarfmode.MenuOverlay:renderMapOverlay() (#2119) * add dwarfmode.MenuOverlay:renderMapOverlay() * ensure we move with the viewport when bounds_rect is nil --- docs/changelog.txt | 1 + library/lua/gui/dwarfmode.lua | 49 +++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index f4d283099..56d743171 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -81,6 +81,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Lua wrappers for functions reverse-engineered from ambushing unit code: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` - Added `custom-raw-tokens` utility to Lua library for reading tokens added to raws by mods. - ``dwarfmode.MenuOverlay``: if ``sidebar_mode`` attribute is set, automatically manage entering a specific sidebar mode on show and restoring the previous sidebar mode on dismiss +- ``dwarfmode.MenuOverlay``: new class function ``renderMapOverlay`` to assist with drawing tiles over the visible map - ``dwarfmode.enterSidebarMode()``: passing ``df.ui_sidebar_mode.DesignateMine`` now always results in you entering ``DesignateMine`` mode and not ``DesignateChopTrees``, even when you looking at the surface where the default designation mode is ``DesignateChopTrees`` - New string class function: ``string:escape_pattern()`` escapes regex special characters within a string - ``widgets.Panel``: if ``autoarrange_subviews`` is set, ``Panel``\s will now automatically lay out widgets vertically according to their current height. This allows you to have widgets dynamically change height or become visible/hidden and you don't have to worry about recalculating frame layouts diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 15b8cd1e8..e56211233 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -504,6 +504,55 @@ function MenuOverlay:render(dc) MenuOverlay.super.render(self, dc) end end + +-- Framework for managing rendering over the map area. This function is intended +-- to be called from a subclass's onRenderBody() function. +-- +-- get_overlay_char_fn takes a coordinate position and an is_cursor boolean and +-- returns the char to render at that position and, optionally, the foreground +-- and background colors to use to draw the char. If nothing should be rendered +-- at that position, the function should return nil. If no foreground color is +-- specified, it defaults to COLOR_GREEN. If no background color is specified, +-- it defaults to COLOR_BLACK. +-- +-- bounds_rect has elements {x1, x2, y1, y2} in global map coordinates (not +-- screen coordinates). The rect is intersected with the visible map viewport to +-- get the range over which get_overlay_char_fn is called. If bounds_rect is not +-- specified, the entire viewport is scanned. +-- +-- example call from a subclass: +-- function MyMenuOverlaySubclass:onRenderBody() +-- local function get_overlay_char(pos) +-- return safe_index(self.overlay_chars, pos.z, pos.y, pos.x), COLOR_RED +-- end +-- self:renderMapOverlay(get_overlay_char, self.overlay_bounds) +-- end +function MenuOverlay:renderMapOverlay(get_overlay_char_fn, bounds_rect) + local vp = self:getViewport() + local rect = gui.ViewRect{rect=vp, + clip_view=bounds_rect and gui.ViewRect{rect=bounds_rect} or nil} + + -- nothing to do if the viewport is completely separate from the bounds_rect + if rect:isDefunct() then return end + + local dc = gui.Painter.new(self.df_layout.map) + local z = df.global.window_z + local cursor = getCursorPos() + for y=rect.clip_y1,rect.clip_y2 do + for x=rect.clip_x1,rect.clip_x2 do + local pos = xyz2pos(x, y, z) + local overlay_char, fg_color, bg_color = get_overlay_char_fn( + pos, same_xy(cursor, pos)) + if not overlay_char then goto continue end + local stile = vp:tileToScreen(pos) + dc:map(true):seek(stile.x, stile.y): + pen(fg_color or COLOR_GREEN, bg_color or COLOR_BLACK): + char(overlay_char):map(false) + ::continue:: + end + end +end + --fakes a "real" workshop sidebar menu, but on exactly selected workshop WorkshopOverlay = defclass(WorkshopOverlay, MenuOverlay) WorkshopOverlay.focus_path="WorkshopOverlay" From e9a2de08cdcbdcd283198a8cf74ac4e4619bc544 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 30 Apr 2022 07:17:43 +0000 Subject: [PATCH 018/854] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index b8d48430a..9c3164994 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b8d48430aa13570a668464ce40294a589f1f0b1f +Subproject commit 9c31649940797f00ed0bc75c11467ed957802eff From 9643246b185e605c29e1e7dfd205e7dbb63faed5 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 30 Apr 2022 19:20:07 +0000 Subject: [PATCH 019/854] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 9c3164994..54dcb842c 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 9c31649940797f00ed0bc75c11467ed957802eff +Subproject commit 54dcb842c2215f9551c2930a294aeeec8f730578 From fe29bff845e3cd2494c6a63898c5797b886e2a81 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sat, 30 Apr 2022 21:46:47 -0700 Subject: [PATCH 020/854] Adds cxxrandom unit test and fixes interface problems (#2099) * Adds cxxrandom unit test and fixes interface problems * Tightens braces * Adds detection code for Shuffle's seqID/engID * Adds usage examples for cxxrandom * Gives cxxrandom objects id ranges, sort of * Updates changelog * Updates changelog.txt * Increases id space for cxxrandom * Fixes bool distribution error message and improves check * Adds comment explaining the seeded RNG tests for cxxrandom * Fixes type problem for 32bit builds * Reduces loop count a few magnitudes * Fixes a mistake in test.cxxrandom_seed --- docs/Lua API.rst | 39 +++++++- docs/changelog.txt | 3 + plugins/cxxrandom.cpp | 192 ++++++++++++++++--------------------- plugins/lua/cxxrandom.lua | 6 +- test/plugins/cxxrandom.lua | 76 +++++++++++++++ 5 files changed, 202 insertions(+), 114 deletions(-) create mode 100644 test/plugins/cxxrandom.lua diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 4615aaf4f..c0dec4b6e 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4401,7 +4401,7 @@ Native functions (exported to Lua) adds a number to the sequence -- ``ShuffleSequence(rngID, seqID)`` +- ``ShuffleSequence(seqID, rngID)`` shuffles the number sequence @@ -4464,7 +4464,7 @@ Lua plugin classes ``bool_distribution`` ~~~~~~~~~~~~~~~~~~~~~ -- ``init(min, max)``: constructor +- ``init(chance)``: constructor - ``next(id)``: returns next boolean in the distribution - ``id``: engine ID to pass to native function @@ -4477,6 +4477,41 @@ Lua plugin classes - ``shuffle()``: shuffles the sequence of numbers - ``next()``: returns next number in the sequence +Usage +----- + +The basic idea is you create a number distribution which you generate random numbers along. The C++ relies +on engines keeping state information to determine the next number along the distribution. +You're welcome to try and (ab)use this knowledge for your RNG purposes. + +Example:: + + local rng = require('plugins.cxxrandom') + local norm_dist = rng.normal_distribution(6820,116) // avg, stddev + local engID = rng.MakeNewEngine(0) + -- somewhat reminiscent of the C++ syntax + print(norm_dist:next(engID)) + + -- a bit more streamlined + local cleanup = true --delete engine on cleanup + local number_generator = rng.crng:new(engID, cleanup, norm_dist) + print(number_generator:next()) + + -- simplified + print(rng.rollNormal(engID,6820,116)) + +The number sequences are much simpler. They're intended for where you need to randomly generate an index, perhaps in a loop for an array. You technically don't need an engine to use it, if you don't mind never shuffling. + +Example:: + + local rng = require('plugins.cxxrandom') + local g = rng.crng:new(rng.MakeNewEngine(0), true, rng.num_sequence:new(0,table_size)) + g:shuffle() + for _ = 1, table_size do + func(array[g:next()]) + end + + dig-now ======= diff --git a/docs/changelog.txt b/docs/changelog.txt index 56d743171..06406087a 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `tweak` partial-items: displays percentages on partially-consumed items such as hospital cloth ## Fixes +- `cxxrandom`: fixed exception when calling ``bool_distribution`` +- `cxxrandom`: fixed id order for ShuffleSequence, but adds code to detect which parameter is which so each id is used correctly. 16000 limit before things get weird (previous was 16 bits) - `autofarm` removed restriction on only planting 'discovered' plants - `luasocket` (and others): return correct status code when closing socket connections @@ -64,6 +66,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Include recently-added tweaks in example dfhack.init file, clean up dreamfort onMapLoad.init file ## Documentation +- `cxxrandom`: added usage examples - Add more examples to the plugin skeleton files so they are more informative for a newbie - Lua API.rst added: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` - Update download link and installation instructions for Visual C++ 2015 build tools on Windows diff --git a/plugins/cxxrandom.cpp b/plugins/cxxrandom.cpp index 159edaefc..12f043214 100644 --- a/plugins/cxxrandom.cpp +++ b/plugins/cxxrandom.cpp @@ -37,94 +37,82 @@ DFHACK_PLUGIN("cxxrandom"); #define PLUGIN_VERSION 2.0 color_ostream *cout = nullptr; -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) -{ +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { cout = &out; return CR_OK; } -DFhackCExport command_result plugin_shutdown (color_ostream &out) -{ +DFhackCExport command_result plugin_shutdown (color_ostream &out) { return CR_OK; } -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { return CR_OK; } +#define EK_ID_BASE (1ll << 40) class EnginesKeeper { private: - EnginesKeeper() {} - std::unordered_map m_engines; - uint16_t counter = 0; + EnginesKeeper() = default; + std::unordered_map m_engines; + uint64_t id_counter = EK_ID_BASE; public: - static EnginesKeeper& Instance() - { + static EnginesKeeper& Instance() { static EnginesKeeper instance; return instance; } - uint16_t NewEngine( uint64_t seed ) - { + uint64_t NewEngine( uint64_t seed ) { + auto id = ++id_counter; + CHECK_INVALID_ARGUMENT(m_engines.count(id) == 0); std::mt19937_64 engine( seed != 0 ? seed : std::chrono::system_clock::now().time_since_epoch().count() ); - m_engines[++counter] = engine; - return counter; + m_engines[id] = engine; + return id; } - void DestroyEngine( uint16_t id ) - { + void DestroyEngine( uint64_t id ) { m_engines.erase( id ); } - void NewSeed( uint16_t id, uint64_t seed ) - { + void NewSeed( uint64_t id, uint64_t seed ) { CHECK_INVALID_ARGUMENT( m_engines.find( id ) != m_engines.end() ); m_engines[id].seed( seed != 0 ? seed : std::chrono::system_clock::now().time_since_epoch().count() ); } - std::mt19937_64& RNG( uint16_t id ) - { + std::mt19937_64& RNG( uint64_t id ) { CHECK_INVALID_ARGUMENT( m_engines.find( id ) != m_engines.end() ); return m_engines[id]; } }; -uint16_t GenerateEngine( uint64_t seed ) -{ +uint64_t GenerateEngine( uint64_t seed ) { return EnginesKeeper::Instance().NewEngine( seed ); } -void DestroyEngine( uint16_t id ) -{ +void DestroyEngine( uint64_t id ) { EnginesKeeper::Instance().DestroyEngine( id ); } -void NewSeed( uint16_t id, uint64_t seed ) -{ +void NewSeed( uint64_t id, uint64_t seed ) { EnginesKeeper::Instance().NewSeed( id, seed ); } -int rollInt(uint16_t id, int min, int max) -{ +int rollInt(uint64_t id, int min, int max) { std::uniform_int_distribution ND(min, max); return ND(EnginesKeeper::Instance().RNG(id)); } -double rollDouble(uint16_t id, double min, double max) -{ +double rollDouble(uint64_t id, double min, double max) { std::uniform_real_distribution ND(min, max); return ND(EnginesKeeper::Instance().RNG(id)); } -double rollNormal(uint16_t id, double mean, double stddev) -{ +double rollNormal(uint64_t id, double mean, double stddev) { std::normal_distribution ND(mean, stddev); return ND(EnginesKeeper::Instance().RNG(id)); } -bool rollBool(uint16_t id, float p) -{ +bool rollBool(uint64_t id, float p) { std::bernoulli_distribution ND(p); return ND(EnginesKeeper::Instance().RNG(id)); } @@ -137,118 +125,104 @@ private: std::vector m_numbers; public: NumberSequence(){} - NumberSequence( int64_t start, int64_t end ) - { - for( int64_t i = start; i <= end; ++i ) - { + NumberSequence( int64_t start, int64_t end ) { + for( int64_t i = start; i <= end; ++i ) { m_numbers.push_back( i ); } } void Add( int64_t num ) { m_numbers.push_back( num ); } - void Reset() { m_numbers.clear(); } - int64_t Next() - { - if(m_position >= m_numbers.size()) - { + void Reset() { m_numbers.clear(); } + int64_t Next() { + if(m_position >= m_numbers.size()) { m_position = 0; } return m_numbers[m_position++]; } - void Shuffle( uint16_t id ) - { - std::shuffle( std::begin( m_numbers ), std::end( m_numbers ), EnginesKeeper::Instance().RNG( id ) ); + void Shuffle( uint64_t engID ) { + std::shuffle( std::begin( m_numbers ), std::end( m_numbers ), EnginesKeeper::Instance().RNG(engID)); } - void Print() - { - for( auto v : m_numbers ) - { + void Print() { + for( auto v : m_numbers ) { cout->print( "%" PRId64 " ", v ); } } }; +#define SK_ID_BASE 0 + class SequenceKeeper { private: - SequenceKeeper() {} - std::unordered_map m_sequences; - uint16_t counter = 0; + SequenceKeeper() = default; + std::unordered_map m_sequences; + uint64_t id_counter = SK_ID_BASE; public: - static SequenceKeeper& Instance() - { + static SequenceKeeper& Instance() { static SequenceKeeper instance; return instance; } - uint16_t MakeNumSequence( int64_t start, int64_t end ) - { - m_sequences[++counter] = NumberSequence( start, end ); - return counter; - } - uint16_t MakeNumSequence() - { - m_sequences[++counter] = NumberSequence(); - return counter; - } - void DestroySequence( uint16_t id ) - { - m_sequences.erase( id ); - } - void AddToSequence( uint16_t id, int64_t num ) - { - CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); - m_sequences[id].Add( num ); - } - void Shuffle( uint16_t id, uint16_t rng_id ) - { - CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); - m_sequences[id].Shuffle( rng_id ); - } - int64_t NextInSequence( uint16_t id ) - { - CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); - return m_sequences[id].Next(); - } - void PrintSequence( uint16_t id ) - { - CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); - auto seq = m_sequences[id]; + uint64_t MakeNumSequence( int64_t start, int64_t end ) { + auto id = ++id_counter; + CHECK_INVALID_ARGUMENT(m_sequences.count(id) == 0); + m_sequences[id] = NumberSequence(start, end); + return id; + } + uint64_t MakeNumSequence() { + auto id = ++id_counter; + CHECK_INVALID_ARGUMENT(m_sequences.count(id) == 0); + m_sequences[id] = NumberSequence(); + return id; + } + void DestroySequence( uint64_t seqID ) { + m_sequences.erase(seqID); + } + void AddToSequence(uint64_t seqID, int64_t num ) { + CHECK_INVALID_ARGUMENT(m_sequences.find(seqID) != m_sequences.end()); + m_sequences[seqID].Add(num); + } + void Shuffle(uint64_t seqID, uint64_t engID ) { + uint64_t sid = seqID >= SK_ID_BASE ? seqID : engID; + uint64_t eid = engID >= EK_ID_BASE ? engID : seqID; + CHECK_INVALID_ARGUMENT(m_sequences.find(sid) != m_sequences.end()); + m_sequences[sid].Shuffle(eid); + } + int64_t NextInSequence( uint64_t seqID ) { + CHECK_INVALID_ARGUMENT(m_sequences.find(seqID) != m_sequences.end()); + return m_sequences[seqID].Next(); + } + void PrintSequence( uint64_t seqID ) { + CHECK_INVALID_ARGUMENT(m_sequences.find(seqID) != m_sequences.end()); + auto seq = m_sequences[seqID]; seq.Print(); } }; -uint16_t MakeNumSequence( int64_t start, int64_t end ) -{ - if( start == end ) - { +uint64_t MakeNumSequence( int64_t start, int64_t end ) { + if (start == end) { return SequenceKeeper::Instance().MakeNumSequence(); } - return SequenceKeeper::Instance().MakeNumSequence( start, end ); + return SequenceKeeper::Instance().MakeNumSequence(start, end); } -void DestroyNumSequence( uint16_t id ) -{ - SequenceKeeper::Instance().DestroySequence( id ); +void DestroyNumSequence( uint64_t seqID ) { + SequenceKeeper::Instance().DestroySequence(seqID); } -void AddToSequence( uint16_t id, int64_t num ) -{ - SequenceKeeper::Instance().AddToSequence( id, num ); +void AddToSequence(uint64_t seqID, int64_t num ) { + SequenceKeeper::Instance().AddToSequence(seqID, num); } -void ShuffleSequence( uint16_t rngID, uint16_t id ) -{ - SequenceKeeper::Instance().Shuffle( id, rngID ); +void ShuffleSequence(uint64_t seqID, uint64_t engID ) { + SequenceKeeper::Instance().Shuffle(seqID, engID); } -int64_t NextInSequence( uint16_t id ) -{ - return SequenceKeeper::Instance().NextInSequence( id ); +int64_t NextInSequence( uint64_t seqID ) { + return SequenceKeeper::Instance().NextInSequence(seqID); } -void DebugSequence( uint16_t id ) -{ - SequenceKeeper::Instance().PrintSequence( id ); +void DebugSequence( uint64_t seqID ) { + SequenceKeeper::Instance().PrintSequence(seqID); } diff --git a/plugins/lua/cxxrandom.lua b/plugins/lua/cxxrandom.lua index 78e363bef..542575b9c 100644 --- a/plugins/lua/cxxrandom.lua +++ b/plugins/lua/cxxrandom.lua @@ -151,8 +151,8 @@ bool_distribution = {} function bool_distribution:new(chance) local o = {} self.__index = self - if type(min) ~= 'number' or type(max) ~= 'number' then - error("Invalid arguments in bool_distribution construction. min and max must be numbers.") + if type(chance) ~= 'number' or chance < 0 or chance > 1 then + error("Invalid arguments in bool_distribution construction. chance must be a number between 0.0 and 1.0 (both included).") end o.p = chance setmetatable(o,self) @@ -208,7 +208,7 @@ function num_sequence:shuffle() if self.rngID == 'nil' then error("Add num_sequence object to crng as distribution, before attempting to shuffle.") end - ShuffleSequence(self.rngID, self.seqID) + ShuffleSequence(self.seqID, self.rngID) end return _ENV diff --git a/test/plugins/cxxrandom.lua b/test/plugins/cxxrandom.lua new file mode 100644 index 000000000..6b11e1937 --- /dev/null +++ b/test/plugins/cxxrandom.lua @@ -0,0 +1,76 @@ +local rng = require('plugins.cxxrandom') + +function test.cxxrandom_distributions() + rng.normal_distribution:new(0,5) + rng.real_distribution:new(-1,1) + rng.int_distribution:new(-20,20) + rng.bool_distribution:new(0.00000000001) + rng.num_sequence:new(-1000,1000) + -- no errors, no problem +end + +--[[ +The below tests pass with their given seeds, if they begin failing +for a given platform, or all around, new seeds should be found. + +Note: these tests which assert RNG, are mere sanity checks +to ensure things haven't been severely broken by any changes +]] + +function test.cxxrandom_seed() + local nd = rng.normal_distribution:new(0,500000) + local e1 = rng.MakeNewEngine(1) + local e2 = rng.MakeNewEngine(1) + local e3 = rng.MakeNewEngine(2) + local g1 = rng.crng:new(e1, true, nd) + local g2 = rng.crng:new(e2, true, nd) + local g3 = rng.crng:new(e3, true, nd) + local v1 = g1:next() + expect.eq(v1, g2:next()) + expect.ne(v1, g3:next()) +end + +function test.cxxrandom_ranges() + local e1 = rng.MakeNewEngine(1) + local g1 = rng.crng:new(e1, true, rng.normal_distribution:new(0,1)) + local g2 = rng.crng:new(e1, true, rng.real_distribution:new(-5,5)) + local g3 = rng.crng:new(e1, true, rng.int_distribution:new(-5,5)) + local g4 = rng.crng:new(e1, true, rng.num_sequence:new(-5,5)) + for i = 1, 10 do + local a = g1:next() + local b = g2:next() + local c = g3:next() + local d = g4:next() + expect.ge(a, -5) + expect.ge(b, -5) + expect.ge(c, -5) + expect.ge(d, -5) + expect.le(a, 5) + expect.le(b, 5) + expect.le(c, 5) + expect.le(d, 5) + end + local gb = rng.crng:new(e1, true, rng.bool_distribution:new(0.00000000001)) + for i = 1, 10 do + expect.false_(gb:next()) + end +end + +function test.cxxrandom_exports() + local id = rng.GenerateEngine(0) + rng.NewSeed(id, 2022) + expect.ge(rng.rollInt(id, 0, 1000), 0) + expect.ge(rng.rollDouble(id, 0, 1), 0) + expect.ge(rng.rollNormal(id, 5, 1), 0) + expect.true_(rng.rollBool(id, 0.9999999999)) + local sid = rng.MakeNumSequence(0,8) + rng.AddToSequence(sid, 9) + rng.ShuffleSequence(sid, id) + for i = 1, 10 do + local v = rng.NextInSequence(sid) + expect.ge(v, 0) + expect.le(v, 9) + end + rng.DestroyNumSequence(sid) + rng.DestroyEngine(id) +end From 88f3627ef489c8ef575fd2fd582a7946ff028543 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 1 May 2022 01:04:47 -0400 Subject: [PATCH 021/854] CI: Pass --color to sphinx --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4af9f5653..252b97a43 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -121,7 +121,7 @@ jobs: submodules: true - name: Build docs run: | - sphinx-build -W --keep-going -j3 . docs/html + sphinx-build -W --keep-going -j3 --color . docs/html - name: Upload docs uses: actions/upload-artifact@v1 with: From 71209880446ae77df3b092dd99aff54d47dce452 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sun, 1 May 2022 07:17:58 +0000 Subject: [PATCH 022/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 54dcb842c..e41298cfb 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 54dcb842c2215f9551c2930a294aeeec8f730578 +Subproject commit e41298cfb52427eb9a30366b292ffee80bedbd9a diff --git a/scripts b/scripts index d9e390cd5..d7220a486 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit d9e390cd5509458bfbc0c1e2ecb01ae96bddc015 +Subproject commit d7220a4869d428a6c51f74282e45754d424e79da From 876d9132b1b07fa7128d9096e1395f10667e7206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20Lul=C3=A9?= Date: Sun, 1 May 2022 15:31:59 +0200 Subject: [PATCH 023/854] Add a link to the rust library for interacting with the remote API (#2121) * Add link to the rust api client library Add a link to https://docs.rs/dfhack-remote/latest/dfhack_remote/index.html a library interacting with DFHack remote API for the Rust programming language * Use the docs.rs link * Fix duplicate link label, included the change in the changelog Co-authored-by: pierre --- docs/Remote.rst | 3 ++- docs/changelog.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Remote.rst b/docs/Remote.rst index c41a14058..de741f66a 100644 --- a/docs/Remote.rst +++ b/docs/Remote.rst @@ -75,10 +75,11 @@ from other (non-C++) languages, including: - `RemoteClientDF-Net `_ for C# - `dfhackrpc `_ for Go -- `dfhack-remote `_ for JavaScript +- `dfhack-remote `__ for JavaScript - `dfhack-client-qt `_ for C++ with Qt - `dfhack-client-python `_ for Python (adapted from :forums:`"Blendwarf" <178089>`) - `dfhack-client-java `_ for Java +- `dfhack-remote `__ for Rust Protocol description diff --git a/docs/changelog.txt b/docs/changelog.txt index 06406087a..cc7f2af06 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -74,6 +74,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `confirm`: Correct the command name in the plugin help text - ``Quickfort Blueprint Editing Guide``: added screenshots to the Dreamfort case study and overall clarified text - Document DFHack `lua-string` (``startswith``, ``endswith``, ``split``, ``trim``, ``wrap``, and ``escape_pattern``). +- Added a Rust client library to the `remote interface docs ` ## API - Added functions reverse-engineered from ambushing unit code: ``Units::isHidden``, ``Units::isFortControlled``, ``Units::getOuterContainerRef``, ``Items::getOuterContainerRef`` From 5f1d2a08e49b1b3d6e0ed41fc6cdf84cc134e4b6 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sun, 1 May 2022 13:33:50 +0000 Subject: [PATCH 024/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index e41298cfb..5fe90d06e 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e41298cfb52427eb9a30366b292ffee80bedbd9a +Subproject commit 5fe90d06ebad4f9686545b7c961c2c07b8bcb799 diff --git a/scripts b/scripts index d7220a486..74f03c0e4 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit d7220a4869d428a6c51f74282e45754d424e79da +Subproject commit 74f03c0e4a5a7818b7a1cbc3576ce2c3d30d3696 From 4439333f58061063ec463ae2943ed79cd637afa2 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 1 May 2022 08:55:25 -0700 Subject: [PATCH 025/854] Adds ccache to build workflow (#2122) * Adds ccache to build workflow * Revises ccache actions cache key --- .github/workflows/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 252b97a43..e8506ae9b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,6 +29,7 @@ jobs: run: | sudo apt-get update sudo apt-get install \ + ccache \ libgtk2.0-0 \ libncursesw5 \ libsdl-image1.2-dev \ @@ -60,6 +61,11 @@ jobs: with: path: ~/DF key: ${{ steps.env_setup.outputs.df_version }} + - name: Fetch ccache + uses: actions/cache@v2 + with: + path: ~/.ccache + key: ccache-${{ matrix.os }}-gcc-${{ matrix.gcc }} - name: Download DF run: | sh ci/download-df.sh From e077e95f690c7163d832e2a3f31028c92d3dcdbd Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 1 May 2022 13:23:41 -0400 Subject: [PATCH 026/854] Add pre-commit hook to check Authors.rst --- .pre-commit-config.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 47f314e2a..26d135ca2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,4 +32,12 @@ repos: exclude_types: - json # specific to dfhack: +- repo: local + hooks: + - id: authors-rst + name: Check Authors.rst + language: python + entry: python3 ci/authors-rst.py + files: docs/Authors\.rst + pass_filenames: false exclude: '^(depends/|data/examples/.*\.json$|.*\.diff$)' From cc20d9fbb797c3da00c6318714ef595994ab57e7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 1 May 2022 13:35:30 -0400 Subject: [PATCH 027/854] Update Authors.rst Checked as far back as 0.47.04-r1. Oldest missing was chrismdp from before 0.47.05-r3. --- docs/Authors.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index a6fa5a3d8..38fe19862 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -34,6 +34,7 @@ brndd brndd burneddi Caldfir caldfir Carter Bray Qartar Chris Dombroski cdombroski +Chris Parsons chrismdp Clayton Hughes Clément Vuchener cvuchener daedsidog daedsidog @@ -47,6 +48,7 @@ Deon DoctorVanGogh DoctorVanGogh Donald Ruegsegger hashaash doomchild doomchild +DwarvenM DwarvenM ElMendukol ElMendukol enjia2000 Eric Wald eswald @@ -59,6 +61,7 @@ Gabe Rau gaberau gchristopher gchristopher George Murray GitOnUp grubsteak grubsteak +Guilherme Abraham GuilhermeAbraham Harlan Playford playfordh Hayati Ayguen hayguen Herwig Hochleitner bendlas @@ -112,6 +115,7 @@ napagokc napagokc Neil Little nmlittle Nick Rart nickrart comestible Nicolas Ayala nicolasayala +Nik Nyby nikolas Nikolay Amiantov abbradar nocico nocico Omniclasm @@ -123,6 +127,7 @@ Paul Fenwick pjf PeridexisErrant PeridexisErrant Petr Mrázek peterix Pfhreak Pfhreak +Pierre Lulé plule Pierre-David Bélanger pierredavidbelanger potato Priit Laes plaes @@ -173,6 +178,7 @@ Theo Kalfas teolandon therahedwig therahedwig ThiagoLira ThiagoLira thurin thurin +Tim Siegel softmoth Tim Walberg twalberg Timothy Collett danaris Timur Kelman TymurGubayev From 2328c530e26e966742cb4c075f6d403011db1bc1 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 2 May 2022 07:18:18 +0000 Subject: [PATCH 028/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 74f03c0e4..714ef87ae 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 74f03c0e4a5a7818b7a1cbc3576ce2c3d30d3696 +Subproject commit 714ef87aef713af94874c6441e7810b9cf9ef312 From 8703eeef9128433dfcbebf9c27bf67d48c5ff36a Mon Sep 17 00:00:00 2001 From: Myk Date: Mon, 2 May 2022 09:29:02 -0700 Subject: [PATCH 029/854] [dreamfort] pull in changes from Google Drive sources (#2124) * update dreamfort from online spreadsheets * add zones for guildhall level --- data/blueprints/library/dreamfort.csv | 66 ++++++++++++++++++++------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index c58ce9111..07aeb09a0 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -28,7 +28,7 @@ "" "Dreamfort works best at an embark site that is flat and has at least one soil layer. New players should avoid embarks with aquifers if they are not prepared to deal with them. Bring picks for mining, an axe for woodcutting, and an anvil for a forge. Bring a few blocks to speed up initial workshop construction as well. That's all you really need, but see the example embark profile in the online spreadsheets for a more complete setup." "" -"Other DFHack scripts and plugins also work very well with Dreamfort, such as autofarm, automelt, autonestbox, burial, prioritize, seedwatch, tailor, and, of course, buildingplan. An init file that configures all these plugins is distributed with DFHack as hack/examples/init/onMapLoad_dreamfort.init." +"Other DFHack commands also work very well with Dreamfort, such as autofarm, autonestbox, prioritize, seedwatch, tailor, and, of course, buildingplan. An init file that gets everything configured for you is distributed with DFHack as hack/examples/init/onMapLoad_dreamfort.init." Put that file in your Dwarf Fortress directory -- the same directory that has dfhack.init. "" "Also copy the files in hack/examples/orders/ to dfhack-config/orders/ and the files in hack/examples/professions/ to professions/. We'll be using these files later. See https://docs.dfhack.org/en/stable/docs/guides/examples-guide.html for more information, including suggestions on how many dwarves of each profession you are likely to need at each stage of fort maturity." @@ -46,7 +46,7 @@ interactively." "# The dreamfort.csv distributed with DFHack is generated with the following command: for fname in dreamfort*.xlsx; do xlsx2csv -a -p '' ""$fname""; done | sed 's/,*$//'" #notes label(checklist) command checklist -"Here is the recommended order for Dreamfort commands. You can copy/paste the command lines directly into the DFHack terminal, or, if you prefer, you can run the blueprints in the UI with gui/quickfort. See the level walkthroughs for context and details. Also remember to read the messages the blueprints print out after you run them so you don't miss any important manual steps." +"Here is the recommended order for Dreamfort commands. You can copy/paste the command lines directly into the DFHack terminal, or, if you prefer, you can run the blueprints in the UI with gui/quickfort. See the walkthroughs (the ""help"" blueprints) for context and details. Also remember to read the messages the blueprints print out after you run them so you don't miss any important manual steps." "" -- Preparation (before you embark!) -- Copy hack/examples/init/onMapLoad_dreamfort.init to your DF directory @@ -67,7 +67,7 @@ quickfort run library/dreamfort.csv -n /surface1,# Run when you find your center quickfort run library/dreamfort.csv -n /dig_all,"# Run when you find a suitable rock layer for the industry level. It designates digging for industry, services, guildhall, suites, and apartments all in one go. This list does not include the farming level, which we'll dig in the uppermost soil layer a bit later. Note that it is more efficient for your miners if you designate your digging before they dig the central stairs past that level since the stairs are dug at a low priority. This keeps your miners focused on one level at a time. If you need to designate your levels individually due to caverns interrupting the sequence or just because it is your preference, run the level-specific dig blueprints (i.e. /industry1, /services1, /guildhall1, /suites1, and 5 levels of /apartments1) instead of running /dig_all." "" -- Core fort (should finish at about the third migration wave) -- -quickfort run library/dreamfort.csv -n /surface2,# Run after initial trees are cleared. +quickfort run library/dreamfort.csv -n /surface2,"# Run after initial trees are cleared. If you are deconstructing the wagon now, wait until after the wagon is deconstructed so the jobs that depend on wagon contents (e.g. blocks) don't get canceled later." quickfort run library/dreamfort.csv -n /farming1,# Run when channels are dug and the additional designated trees are cleared. quickfort run library/dreamfort.csv -n /farming2,# Run when the farming level has been dug out. quickfort run library/dreamfort.csv -n /surface3,# Run right after /farming2. @@ -78,7 +78,7 @@ quickfort run library/dreamfort.csv -n /surface4,"# Run after the walls and floo "quickfort run,orders library/dreamfort.csv -n /services2",# Run when the services levels have been dug out. Feel free to remove the orders for the ropes if you already brought them with you. orders import basic,"# Run after the first migration wave, so you have dorfs to do all the basic tasks. Note that this is the ""orders"" plugin, not the ""quickfort orders"" command." "quickfort run,orders library/dreamfort.csv -n /surface5","# Run when all marked trees on the surface are chopped down and walls and floors have been constructed, including the roof section over the future barracks." -prioritize ConstructBuilding,# Run when you see the bridges ready to be built so the masons come and actually build them. +prioritize ConstructBuilding,# Run when you see the bridges ready to be built so the busy masons come and build them. "quickfort run,orders library/dreamfort.csv -n /surface6",# Run when at least the beehives and weapon rack are constructed and you have linked all levers to their respective bridges. "quickfort run,orders library/dreamfort.csv -n /surface7",# Run after the surface walls are completed and any marked trees are chopped down. "" @@ -94,6 +94,7 @@ orders import furnace,# Automated production of basic furnace-related items. Don "quickfort run,orders library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed on the first apartments level. "quickfort run,orders library/dreamfort.csv -n /services3","# Run after the dining table and chair, weapon rack, and archery targets have been constructed. Also wait until after you complete /surface7, though, because surface defenses are more important than a grand dining hall." "quickfort run,orders library/dreamfort.csv -n /guildhall2",# Run when the guildhall level has been dug out. +"quickfort run,orders library/dreamfort.csv -n /guildhall3",# Optionally run after /guildhall2 to build default furnishings. "quickfort run,orders library/dreamfort.csv -n /farming4",# Run once you have a cache of potash. orders import military,# Automated production of military equipment. Turn on automelt in the meltables piles on the industry level to automatically upgrade all metal military equipment to masterwork quality. These orders are optional if you are not using a military. orders import smelting,# Automated production of all types of metal bars. @@ -104,7 +105,7 @@ orders import glassstock,# Maintains a small stock of all types of glass furnitu -- Repeat for each remaining apartments level as needed -- "quickfort run,orders library/dreamfort.csv -n /apartments2",# Run when the apartment level has been dug out. "quickfort run,orders library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed. -burial -pets,# Run once the coffins are placed to set them to allow for burial. This is handled for you if you are using the provided onMapLoad_dreamfort.init file. +burial -pets,# Run once the coffins are placed to set them to allow for burial. See this checklist online at https://docs.google.com/spreadsheets/d/13PVZ2h3Mm3x_G1OXQvwKd7oIR2lK4A1Ahf6Om1kFigw/edit#gid=1459509569 #notes label(setup_help) @@ -113,14 +114,14 @@ Makes common initial adjustments to in-game settings. "The /setup blueprint is intended to be run once at the start of the game, before anything else is changed. Players are welcome to make any further customizations after this blueprint is run. Please be sure to run the /setup blueprint before making any other changes, though, so your settings are not overwritten!" "" The following settings are changed: -"- The manager, chief medical dwarf, broker, and bookkeeper noble roles are assigned to the first suggested dwarf. This is likely to be the expedition leader, but the game could suggest others if they have relevant skills. Bookkeeping is also set to the highest precision." +"- The manager, chief medical dwarf, broker, and bookkeeper noble roles are assigned to the first suggested dwarf. This is likely to be the expedition leader, but the game could suggest others if they have relevant skills. Bookkeeping is set to the highest precision." "" - Standing orders are set to: - only farmers harvest - gather refuse from outside (incl. vermin) - - no autoloom (we'll be managing cloth production with automated orders) + - no autoloom (so the hospital always has thread -- we'll be managing cloth production with automated orders) "" -"- A burrow named ""Inside"" is created (it's up to the player to define the area). It is intended for use in getting your civilians to safety during sieges. An alert named ""Siege"" is also created and associated with the ""Inside"" burrow." +"- A burrow named ""Inside"" is created, but it's up to the player to define the area as the fort expands. It is intended for use in getting your civilians to safety during sieges. A military alert named ""Siege"" is also created and associated with the ""Inside"" burrow." "" - Military uniforms get the following modifications: - all default uniforms set to replace clothing @@ -144,7 +145,7 @@ starthotkeys: H{sethotkey fkey={F2} name=Farming}{sethotkey fkey={F3} name=Indus "" "#config label(setup) message(Please set the zoom targets of the hotkeys (the 'H' menu) according to where you actually end up digging the levels. As you build your fort, expand the ""Inside"" burrow to include new civilian-safe areas. -Optionally, add a leather cloak to your military uniforms to enhance the protection of the uniforms. +Optionally, add a leather cloak to your military uniforms to enhance their protection rating. Nothing in Dreamfort depends on these settings staying as they are. Feel free to change any setting to your personal preference.) assign nobles, set standing orders, create burrows, make adjustments to military uniforms, and set hotkey names" {startnobles}{startorders}{startburrows}{startmilitary}{starthotkeys} "#meta label(dig_all) start(central stairs on industry level) dig industry, services, guildhall, suites, and apartments levels. does not include farming." @@ -532,7 +533,7 @@ Feel free to assign an unimportant animal to the pasture in the main entranceway -"#place label(surface_place_start) start(19; 19) hidden() message(if you haven't already, now is a good time to deconstruct the wagon) starting stockpiles" +#place label(surface_place_start) start(19; 19) hidden() starting stockpiles @@ -974,7 +975,7 @@ You might also want to set the ""trade goods quantum"" stockpile to Auto Trade i ,,,`,,`,nocontainers,crafts,,,,,,,`,,,,"{givename name=""inner main gate""}",,,,`,,,,,,,,,`,,` ,,,`,,`,"{givename name=""trade goods""}",,,,,,,,,,,,,,,,,,,,,,,,,`,,` ,,,`,,`,{forbidmasterworkfinishedgoods}{forbidartifactfinishedgoods},,,,,,,,"{givename name=""trade depo gate""}",,,,,,,,"{givename name=""barracks gate""}",,,,,,,,,`,,` -,,,`,,`,,"{quantumstopfromnorth name=""Trade Goods Dumper""}",,,,,,,,,,,,,,,,,"{quantumstop name=""Prisoner quantum"" move={Up 5} move_back={Down 5} route_enable={prisoner_route_enable}}{givename name=""prisoner dumper""}",,,,,,,`,,` +,,,`,,`,,"{quantumstopfromnorth name=""Trade Goods quantum""}",,,,,,,,,,,,,,,,,"{quantumstop name=""Prisoner quantum"" move={Up 5} move_back={Down 5} route_enable={prisoner_route_enable}}{givename name=""prisoner dumper""}",,,,,,,`,,` ,,,`,,`,,"{quantum name=""trade goods quantum""}",,,,,,,`,,,,,,,,`,,"{quantum name=""prisoner quantum"" quantum_enable={enableanimals}}",,,,,,,`,,` ,,,`,,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,,` ,,,`,,"{givename name=""left outer gate""}",,,,,,,,,"{givename name=""left inner gate""}",,,,,,,,"{givename name=""right inner gate""}",,,,,,,,,"{givename name=""right outer gate""}",,` @@ -2554,14 +2555,14 @@ query_jail/services_query_jail Screenshot: https://drive.google.com/file/d/17jHiCKeZm6FSS-CI4V0r0GJZh09nzcO_ "" Features: -"- Big rooms, optionally pre-furnished. Double-thick walls to ensure engravings add value to the ""correct"" side. Fill with furniture and assign as needed." +"- Big rooms, optionally pre-furnished. Double-thick walls to ensure engravings add value to the ""correct"" side. Declare locations as needed." "" Guildhall Walkthrough: 1) Dig out the rooms with /guildhall1. "" -"2) Once the area is dug out, add doors and a few statues with /guildhall2. Run ""quickfort orders"" for /guildhall2." +"2) Once the area is dug out, pre-create the zones and add doors and a few statues with /guildhall2. Run ""quickfort orders"" for /guildhall2." "" -"3) Furnish individual rooms manually, or alternately get default furnishings for a variety of room types by running /guildhall3. Declare appropriate locations as you need guildhalls, libraries, and temples. If you need more rooms, you can dig another /guildhall1 in an unused z-level." +"3) Furnish individual rooms manually, or get default furnishings for a variety of room types by running /guildhall3. Declare appropriate locations from the pre-created zones as you need guildhalls, libraries, and temples. If you need more rooms, you can dig another /guildhall1 in an unused z-level." "#dig label(guildhall1) start(15; 15; central stairs) message(Once the area is dug out, continue with /guildhall2.)" @@ -2592,7 +2593,10 @@ Guildhall Walkthrough: ,,d,d,d,d,d,d,d,,,d,d,d,d,d,d,d,,,d,d,d,d,d,d,d -"#build label(guildhall2) start(15; 15; central stairs) message(Remember to enqueue manager orders for this blueprint. +#meta label(guildhall2) +doors/guildhall_doors +zones/guildhall_zones +"#build label(guildhall_doors) start(15; 15; central stairs) hidden() message(Remember to enqueue manager orders for this blueprint. Smooth/engrave tiles, furnish rooms, and declare locations as required.) build doors" @@ -2623,6 +2627,36 @@ Smooth/engrave tiles, furnish rooms, and declare locations as required.) build d ,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +#zone label(guildhall_zones) start(15; 15; central stairs) hidden() designate zones + +,m(9x9),,,,,,,,,m(9x9),,,,,,,,,m(9x9) +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,,,,,,~,,,,,,~,,~,,,,,,~ +,m(9x9),,,,,,~,,,,,,`,~,`,,,,m(9x9),,~ +,,`,`,`,`,`,`,`,,,,,~,,~,,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,~,`,~,`,,,,`,~,`,~,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,~,,`,,`,,`,,~,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,~,`,~,`,,,,`,~,`,~,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,,~,,~,,,,,`,`,`,`,`,`,` +,,,,,,,~,,,,,,`,~,`,,,,,,~ +,m(9x9),,,,,,~,,,m(9x9),,,~,,~,,,,m(9x9),,~ +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` + + "#build label(guildhall3) start(15; 15; central stairs) message(Remember to enqueue manager orders for this blueprint.) furnish 4 guildhalls, 3 temples, and a library" @@ -2676,7 +2710,7 @@ Apartments Walkthrough: "" "3) Once the beds are built, configure the rooms and build the remaining furniture with /apartments3. Run ""quickfort orders"" for /apartments3." "" -"4) Once the coffins are all in place, run ""burial -pets"" to set them all to accept burials. This is handled for you if you're using the onMapLoad_dreamfort.init file included with DFHack." +"4) Once the coffins are all in place, run ""burial -pets"" to set them all to accept burials." "#dig label(suites1) start(18; 18; central ramp) message(Once the area is dug out, run /suites2) noble suites" ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d From 0389637ac89520cba35dcd539adadb87d1e4330e Mon Sep 17 00:00:00 2001 From: Myk Date: Mon, 2 May 2022 21:08:09 -0700 Subject: [PATCH 030/854] clean up changelog.txt --- docs/changelog.txt | 90 +++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index cc7f2af06..315f1a4b9 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -34,71 +34,69 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## New Plugins -- `spectate`: automates the following of dwarves more often than not based on job zlevel activity levels, sometimes randomly though. - -## Removed +- `spectate`: "spectator mode" -- automatically follows dwarves doing things in your fort ## New Tweaks -- `tweak` partial-items: displays percentages on partially-consumed items such as hospital cloth +- `tweak`: ``partial-items`` displays percentage remaining for partially-consumed items such as hospital cloth ## Fixes +- `autofarm`: removed restriction on only planting "discovered" plants - `cxxrandom`: fixed exception when calling ``bool_distribution`` -- `cxxrandom`: fixed id order for ShuffleSequence, but adds code to detect which parameter is which so each id is used correctly. 16000 limit before things get weird (previous was 16 bits) -- `autofarm` removed restriction on only planting 'discovered' plants -- `luasocket` (and others): return correct status code when closing socket connections +- `luasocket`: return correct status code when closing socket connections so clients can know when to retry ## Misc Improvements +- `autochop`: only designate the amount of trees required to reach ``max_logs`` +- `autochop`: preferably designate larger trees over smaller ones +- `blueprint`: ``track`` phase renamed to ``carve`` +- `blueprint`: carved fortifications and (optionally) engravings are now captured in generated blueprints +- `cursecheck`: new option, ``--ids`` prints creature and race IDs of the cursed creature +- `debug`: DFHack log messages now have configurable headers (e.g. timestamp, origin plugin name, etc.) via the ``debugfilter`` command of the `debug` plugin +- `debug`: script execution log messages (e.g. "Loading script: dfhack_extras.init" can now be controlled with the ``debugfilter`` command. To hide the messages, add this line to your ``dfhack.init`` file: ``debugfilter set Warning core script`` +- `dfhack-examples-guide`: add mugs to ``basic`` manager orders +- `dfhack-examples-guide`: ``onMapLoad_dreamfort.init`` remove "cheaty" commands and new tweaks that are now in the default ``dfhack.init-example`` file +- ``dfhack.init-example``: recently-added tweaks added to example ``dfhack.init`` file - `dig-now`: handle fortification carving - `EventManager`: add new event type ``JOB_STARTED``, triggered when a job first gains a worker -- `EventManager`: add new event type ``NEW_UNIT_ACTIVE``, triggered when a new unit appears on the active list -- `EventManager`: now each registered handler for an event can have its own frequency instead of all handlers using the lowest frequency of all handlers -- `stocks`: allow search terms to match the full item label, even when the label is truncated for length -- `dfhack-examples-guide`: add mugs to ``basic`` manager orders +- `EventManager`: add new event type ``UNIT_NEW_ACTIVE``, triggered when a new unit appears on the active list - `gui/create-item`: Added "(chain)" annotation text for armours with the [CHAIN_METAL_TEXT] flag set -- DFHack log messages now have configurable headers (e.g. timestamp, origin plugin name, etc.) via the ``debugfilter`` command of the `debug` plugin -- Script execution log messages (e.g. "Loading script: dfhack_extras.init" can now be controlled with the ``debugfilter`` command. To hide the messages, add this line to your ``dfhack.init`` file: ``debugfilter set Warning core script`` -- `manipulator`: Tweak colors to make the cursor easier to locate -- `autochop`: only designate the amount of trees required to reach ``max_logs`` -- `autochop`: preferably designate larger trees over smaller ones -- `blueprint`: ``track`` phase renamed to ``carve``. carved fortifications and (optionally) engravings are now captured in blueprints -- `tweak` stable-cursor: Keep the cursor stable even when the viewport moves a small amount -- `cursecheck`: Added a new parameter, ``ids``, to print creature and race IDs of the cursed creature. -- Include recently-added tweaks in example dfhack.init file, clean up dreamfort onMapLoad.init file +- `manipulator`: tweak colors to make the cursor easier to locate +- `stocks`: allow search terms to match the full item label, even when the label is truncated for length +- `tweak`: ``stable-cursor`` now keeps the cursor stable even when the viewport moves a small amount ## Documentation +- add more examples to the plugin example skeleton files so they are more informative for a newbie +- `confirm`: correct the command name in the plugin help text - `cxxrandom`: added usage examples -- Add more examples to the plugin skeleton files so they are more informative for a newbie -- Lua API.rst added: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` -- Update download link and installation instructions for Visual C++ 2015 build tools on Windows -- Updated information regarding obtaining a compatible Windows build environment -- `confirm`: Correct the command name in the plugin help text -- ``Quickfort Blueprint Editing Guide``: added screenshots to the Dreamfort case study and overall clarified text -- Document DFHack `lua-string` (``startswith``, ``endswith``, ``split``, ``trim``, ``wrap``, and ``escape_pattern``). -- Added a Rust client library to the `remote interface docs ` +- ``Lua API.rst``: added ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` +- `lua-string`: document DFHack string extensions (``startswith()``, ``endswith()``, ``split()``, ``trim()``, ``wrap()``, and ``escape_pattern()``) +- `quickfort-blueprint-guide`: added screenshots to the Dreamfort case study and overall clarified text +- `remote-client-libs`: add new Rust client library +- update download link and installation instructions for Visual C++ 2015 build tools on Windows +- update information regarding obtaining a compatible Windows build environment ## API -- Added functions reverse-engineered from ambushing unit code: ``Units::isHidden``, ``Units::isFortControlled``, ``Units::getOuterContainerRef``, ``Items::getOuterContainerRef`` +- add functions reverse-engineered from ambushing unit code: ``Units::isHidden()``, ``Units::isFortControlled()``, ``Units::getOuterContainerRef()``, ``Items::getOuterContainerRef()`` ## Lua -- ``widgets.FilteredList`` now allows all punctuation to be typed into the filter and can match search keys that start with punctuation. -- ``widgets.ListBox``: minimum height of dialog is now calculated correctly when there are no items in the list (e.g. when a filter doesn't match anything) -- Lua wrappers for functions reverse-engineered from ambushing unit code: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` -- Added `custom-raw-tokens` utility to Lua library for reading tokens added to raws by mods. -- ``dwarfmode.MenuOverlay``: if ``sidebar_mode`` attribute is set, automatically manage entering a specific sidebar mode on show and restoring the previous sidebar mode on dismiss -- ``dwarfmode.MenuOverlay``: new class function ``renderMapOverlay`` to assist with drawing tiles over the visible map -- ``dwarfmode.enterSidebarMode()``: passing ``df.ui_sidebar_mode.DesignateMine`` now always results in you entering ``DesignateMine`` mode and not ``DesignateChopTrees``, even when you looking at the surface where the default designation mode is ``DesignateChopTrees`` -- New string class function: ``string:escape_pattern()`` escapes regex special characters within a string -- ``widgets.Panel``: if ``autoarrange_subviews`` is set, ``Panel``\s will now automatically lay out widgets vertically according to their current height. This allows you to have widgets dynamically change height or become visible/hidden and you don't have to worry about recalculating frame layouts -- ``widgets.ResizingPanel``: new ``Panel`` subclass that automatically recalculates it's own frame height based on the size, position, and visibility of its subviews -- ``widgets.WrappedLabel``: new ``Label`` subclass that provides autowrapping of text -- ``widgets.TooltipLabel``: new ``WrappedLabel`` subclass that provides tooltip-like behavior -- ``widgets.HotkeyLabel``: new ``Label`` subclass that displays and reacts to hotkeys -- ``widgets.CycleHotkeyLabel``: new ``Label`` subclass that allows users to cycle through a list of options by pressing a hotkey -- ``widgets.ToggleHotkeyLabel``: new ``CycleHotkeyLabel`` subclass that toggles between ``On`` and ``Off`` states -- ``safe_index`` now properly handles lua sparse tables that are indexed by numbers -- ``widgets``: unset values in ``frame_inset``-table default to ``0`` +- `custom-raw-tokens`: library for accessing tokens added to raws by mods +- ``dfhack.units``: Lua wrappers for functions reverse-engineered from ambushing unit code: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` - ``dialogs``: ``show*`` functions now return a reference to the created dialog +- ``dwarfmode.enterSidebarMode()``: passing ``df.ui_sidebar_mode.DesignateMine`` now always results in you entering ``DesignateMine`` mode and not ``DesignateChopTrees``, even when you looking at the surface (where the default designation mode is ``DesignateChopTrees``) +- ``dwarfmode.MenuOverlay``: if ``sidebar_mode`` attribute is set, automatically manage entering a specific sidebar mode on show and restoring the previous sidebar mode on dismiss +- ``dwarfmode.MenuOverlay``: new class function ``renderMapOverlay`` to assist with painting tiles over the visible map - ``ensure_key``: new global function for retrieving or dynamically creating Lua table mappings +- ``safe_index``: now properly handles lua sparse tables that are indexed by numbers +- ``string``: new function ``escape_pattern()`` escapes regex special characters within a string +- ``widgets``: unset values in ``frame_inset`` table default to ``0`` +- ``widgets``: ``FilteredList`` class now allows all punctuation to be typed into the filter and can match search keys that start with punctuation +- ``widgets``: minimum height of ``ListBox`` dialog is now calculated correctly when there are no items in the list (e.g. when a filter doesn't match anything) +- ``widgets``: if ``autoarrange_subviews`` is set, ``Panel``\s will now automatically lay out widgets vertically according to their current height. This allows you to have widgets dynamically change height or become visible/hidden and you don't have to worry about recalculating frame layouts +- ``widgets``: new class ``ResizingPanel`` (subclass of ``Panel``) automatically recalculates its own frame height based on the size, position, and visibility of its subviews +- ``widgets``: new class ``HotkeyLabel`` (subclass of ``Label``) that displays and reacts to hotkeys +- ``widgets``: new class ``CycleHotkeyLabel`` (subclass of ``Label``) allows users to cycle through a list of options by pressing a hotkey +- ``widgets``: new class ``ToggleHotkeyLabel`` (subclass of ``CycleHotkeyLabel``) toggles between ``On`` and ``Off`` states +- ``widgets``: new class ``WrappedLabel`` (subclass of ``Label``) provides autowrapping of text +- ``widgets``: new class ``TooltipLabel`` (subclass of ``WrappedLabel``) provides tooltip-like behavior # 0.47.05-r4 From d4ba946d2b0dedfa6e15af8ce1c71651ad308bed Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 3 May 2022 07:18:10 +0000 Subject: [PATCH 031/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 5fe90d06e..165680bb6 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5fe90d06ebad4f9686545b7c961c2c07b8bcb799 +Subproject commit 165680bb6babc0d5023f987ec89622e5daacb365 diff --git a/scripts b/scripts index 714ef87ae..39c3226af 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 714ef87aef713af94874c6441e7810b9cf9ef312 +Subproject commit 39c3226affd3959b15b296b59bd89a54c32cb5b8 From 585888c2d36593882f756c5f5de459ec6b42b9ab Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 4 May 2022 17:21:31 -0700 Subject: [PATCH 032/854] update version, changelog, modules for 0.47.05-r5 --- CMakeLists.txt | 2 +- docs/changelog.txt | 16 ++++++++++++++++ library/xml | 2 +- scripts | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 054430ba6..7cbf13789 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,7 +192,7 @@ endif() # set up versioning. set(DF_VERSION "0.47.05") -set(DFHACK_RELEASE "r4") +set(DFHACK_RELEASE "r5") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/docs/changelog.txt b/docs/changelog.txt index 315f1a4b9..80c52e046 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -33,6 +33,22 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future +## New Plugins + +## New Tweaks + +## Fixes + +## Misc Improvements + +## Documentation + +## API + +## Lua + +# 0.47.05-r5 + ## New Plugins - `spectate`: "spectator mode" -- automatically follows dwarves doing things in your fort diff --git a/library/xml b/library/xml index 165680bb6..7ec5d8699 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 165680bb6babc0d5023f987ec89622e5daacb365 +Subproject commit 7ec5d86996269df5e01d64ea5bae67d0c29afd77 diff --git a/scripts b/scripts index 39c3226af..b808050d4 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 39c3226affd3959b15b296b59bd89a54c32cb5b8 +Subproject commit b808050d4a3885aa0e250e726708b2b28fe28260 From a646ed7313b13f2f97cd4c93c85f341f4681762c Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 1 May 2022 13:48:54 -0400 Subject: [PATCH 033/854] Split configure + build into separate steps for more granular timing --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e8506ae9b..07febaa63 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,7 +69,7 @@ jobs: - name: Download DF run: | sh ci/download-df.sh - - name: Build DFHack + - name: Configure DFHack env: CC: gcc-${{ matrix.gcc }} CXX: g++-${{ matrix.gcc }} @@ -85,6 +85,8 @@ jobs: -DBUILD_STONESENSE:BOOL=${{ matrix.plugins == 'all' }} \ -DBUILD_SUPPORTED:BOOL=1 \ -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" + - name: Build DFHack + run: | ninja -C build-ci install - name: Run tests id: run_tests From c44a2cfa6a90fe98d68f4a4c9533fb14d4ed3cb7 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 9 May 2022 07:18:27 +0000 Subject: [PATCH 034/854] Auto-update submodules depends/luacov: dfhack --- depends/luacov | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/luacov b/depends/luacov index 87d6ae018..cc76184af 160000 --- a/depends/luacov +++ b/depends/luacov @@ -1 +1 @@ -Subproject commit 87d6ae018cb8d288d854f632e9d8d959d75d7db4 +Subproject commit cc76184af85d371c5ad95ea3bb14283fd1c2a42b From e95d8c729621df2a28fc562f746771075cf8f95c Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 9 May 2022 16:39:47 +0000 Subject: [PATCH 035/854] Auto-update submodules scripts: master depends/luacov: dfhack --- depends/luacov | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/luacov b/depends/luacov index cc76184af..99d068278 160000 --- a/depends/luacov +++ b/depends/luacov @@ -1 +1 @@ -Subproject commit cc76184af85d371c5ad95ea3bb14283fd1c2a42b +Subproject commit 99d06827848583232dd77afb34cd7ab589567086 diff --git a/scripts b/scripts index b808050d4..64626c814 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b808050d4a3885aa0e250e726708b2b28fe28260 +Subproject commit 64626c81481a2f938f071b7347d07c10312b223d From 55e4008925ee5af3a07ca3225ed73d2414423a0d Mon Sep 17 00:00:00 2001 From: Tim Siegel Date: Thu, 21 Apr 2022 16:58:53 -0400 Subject: [PATCH 036/854] MiscUtils: teach word_wrap() to optionally preserve whitespace --- library/MiscUtils.cpp | 58 +++++++++++++++++++++++++++---------- library/include/MiscUtils.h | 3 +- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 56af85afe..6ab63d5eb 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -168,32 +168,58 @@ std::string to_search_normalized(const std::string &str) return result; } -bool word_wrap(std::vector *out, const std::string &str, size_t line_length) + +bool word_wrap(std::vector *out, const std::string &str, + size_t line_length, bool collapse_whitespace) { - out->clear(); - std::istringstream input(str); - std::string out_line; - std::string word; - if (input >> word) + if (line_length == 0) + line_length = SIZE_MAX; + + std::string line; + size_t break_pos = 0; + + for (auto &c : str) { - out_line += word; - // size_t remaining = line_length - std::min(line_length, word.length()); - while (input >> word) + if (c == '\n') + { + out->push_back(line); + line.clear(); + break_pos = 0; + continue; + } + + if (isspace(c)) { - if (out_line.length() + word.length() + 1 <= line_length) + if (break_pos == line.length() && collapse_whitespace) + continue; + + line.push_back(collapse_whitespace ? ' ' : c); + break_pos = line.length(); + } + else { + line.push_back(c); + } + + if (line.length() > line_length) + { + if (break_pos > 0) { - out_line += ' '; - out_line += word; + // Break before last space, and skip that space + out->push_back(line.substr(0, break_pos - 1)); } else { - out->push_back(out_line); - out_line = word; + // Single word is too long, just break it + out->push_back(line.substr(0, line_length)); + break_pos = line_length; } + line = line.substr(break_pos); + break_pos = 0; } - if (out_line.length()) - out->push_back(out_line); } + if (line.length()) + out->push_back(line); + return true; } diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index 7a5f050a2..764b11413 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -391,7 +391,8 @@ DFHACK_EXPORT std::string to_search_normalized(const std::string &str); DFHACK_EXPORT bool word_wrap(std::vector *out, const std::string &str, - size_t line_length = 80); + size_t line_length = 80, + bool collapse_whitespace = false); inline bool bits_match(unsigned required, unsigned ok, unsigned mask) { From c3347d465f2cd8f4f05cc651eb4bbac49c03f5df Mon Sep 17 00:00:00 2001 From: Tim Siegel Date: Wed, 20 Apr 2022 11:59:44 -0400 Subject: [PATCH 037/854] [command-prompt] cosmetics: whitespace, sort headers --- plugins/command-prompt.cpp | 102 ++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/plugins/command-prompt.cpp b/plugins/command-prompt.cpp index a987ea829..0bfe31dc9 100644 --- a/plugins/command-prompt.cpp +++ b/plugins/command-prompt.cpp @@ -1,23 +1,23 @@ -//command-prompt a one line command entry at the top of the screen for quick commands +// command-prompt: A one-line command entry at the top of the screen for quick commands #include "Core.h" +#include #include #include #include -#include -#include #include +#include -#include #include +#include #include #include +#include "df/enabler.h" +#include "df/graphic.h" #include "df/interface_key.h" #include "df/ui.h" -#include "df/graphic.h" -#include "df/enabler.h" using namespace DFHack; using namespace df::enums; @@ -36,8 +36,10 @@ class prompt_ostream:public buffered_color_ostream protected: void flush_proxy(); public: - prompt_ostream(viewscreen_commandpromptst* parent):parent_(parent){} - bool empty(){return buffer.empty();} + prompt_ostream(viewscreen_commandpromptst* parent) + : parent_(parent) + {} + bool empty() { return buffer.empty(); } }; class viewscreen_commandpromptst : public dfhack_viewscreen { public: @@ -48,7 +50,7 @@ public: } void render(); - void help() { } + void help() {} int8_t movies_okay() { return 0; } df::unit* getSelectedUnit() { return Gui::getAnyUnit(parent); } @@ -57,10 +59,11 @@ public: df::plant* getSelectedPlant() { return Gui::getAnyPlant(parent); } std::string getFocusString() { return "commandprompt"; } - viewscreen_commandpromptst(std::string entry):submitted(false), is_response(false) + viewscreen_commandpromptst(std::string entry) + : submitted(false), is_response(false) { - show_fps=gps->display_frames; - gps->display_frames=0; + show_fps = gps->display_frames; + gps->display_frames = 0; cursor_pos = entry.size(); frame = 0; history_idx = command_history.size(); @@ -76,7 +79,7 @@ public: } ~viewscreen_commandpromptst() { - gps->display_frames=show_fps; + gps->display_frames = show_fps; } void add_response(color_value v, std::string s) @@ -125,7 +128,7 @@ public: } protected: - std::list > responses; + std::list > responses; int cursor_pos; int history_idx; bool submitted; @@ -138,8 +141,8 @@ void prompt_ostream::flush_proxy() { if (buffer.empty()) return; - for(auto it=buffer.begin();it!=buffer.end();it++) - parent_->add_response(it->first,it->second); + for(auto it = buffer.begin(); it != buffer.end(); it++) + parent_->add_response(it->first, it->second); buffer.clear(); } void viewscreen_commandpromptst::render() @@ -154,25 +157,26 @@ void viewscreen_commandpromptst::render() auto dim = Screen::getWindowSize(); parent->render(); - if(is_response) + if (is_response) { - auto it=responses.begin(); - for(int i=0;isecond; - Screen::paintString(Screen::Pen(' ',it->first,0),0,i,cur_line.substr(0,cur_line.size()-1)); + Screen::fillRect(Screen::Pen(' ', 7, 0), 0, i, dim.x, i); + std::string cur_line = it->second; + Screen::paintString(Screen::Pen(' ', it->first, 0), 0, i, + cur_line.substr(0, cur_line.size() - 1)); } } else { std::string entry = get_entry(); - Screen::fillRect(Screen::Pen(' ', 7, 0),0,0,dim.x,0); - Screen::paintString(Screen::Pen(' ', 7, 0), 0, 0,"[DFHack]#"); + Screen::fillRect(Screen::Pen(' ', 7, 0), 0, 0, dim.x, 0); + Screen::paintString(Screen::Pen(' ', 7, 0), 0, 0, "[DFHack]#"); std::string cursor = (frame < enabler->gfps / 2) ? "_" : " "; - if(cursor_pos < (dim.x - 10)) + if (cursor_pos < dim.x - 10) { - Screen::paintString(Screen::Pen(' ', 7, 0), 10,0 , entry); + Screen::paintString(Screen::Pen(' ', 7, 0), 10, 0, entry); if (int16_t(entry.size()) > dim.x - 10) Screen::paintTile(Screen::Pen('\032', 7, 0), dim.x - 1, 0); if (cursor != " ") @@ -191,12 +195,12 @@ void viewscreen_commandpromptst::render() void viewscreen_commandpromptst::submit() { CoreSuspendClaimer suspend; - if(is_response) + if (is_response) { Screen::dismiss(this); return; } - if(submitted) + if (submitted) return; submitted = true; prompt_ostream out(this); @@ -204,11 +208,11 @@ void viewscreen_commandpromptst::submit() Screen::Hide hide_guard(this, Screen::Hide::RESTORE_AT_TOP); Core::getInstance().runCommand(out, get_entry()); } - if(out.empty() && responses.empty()) + if (out.empty() && responses.empty()) Screen::dismiss(this); else { - is_response=true; + is_response = true; } } void viewscreen_commandpromptst::feed(std::set *events) @@ -240,14 +244,14 @@ void viewscreen_commandpromptst::feed(std::set *events) for (auto it = events->begin(); it != events->end(); ++it) { auto key = *it; - if (key==interface_key::STRING_A000) //delete? + if (key == interface_key::STRING_A000) //delete? { - if(entry.size() && cursor_pos > 0) + if (entry.size() && cursor_pos > 0) { entry.erase(cursor_pos - 1, 1); cursor_pos--; } - if(size_t(cursor_pos) > entry.size()) + if (size_t(cursor_pos) > entry.size()) cursor_pos = entry.size(); continue; } @@ -261,34 +265,34 @@ void viewscreen_commandpromptst::feed(std::set *events) } } // Prevent number keys from moving cursor - if(events->count(interface_key::CURSOR_RIGHT)) + if (events->count(interface_key::CURSOR_RIGHT)) { cursor_pos++; if (size_t(cursor_pos) > entry.size()) cursor_pos = entry.size(); } - else if(events->count(interface_key::CURSOR_LEFT)) + else if (events->count(interface_key::CURSOR_LEFT)) { cursor_pos--; if (cursor_pos < 0) cursor_pos = 0; } - else if(events->count(interface_key::CURSOR_RIGHT_FAST)) + else if (events->count(interface_key::CURSOR_RIGHT_FAST)) { forward_word(); } - else if(events->count(interface_key::CURSOR_LEFT_FAST)) + else if (events->count(interface_key::CURSOR_LEFT_FAST)) { back_word(); } - else if(events->count(interface_key::CUSTOM_CTRL_A)) + else if (events->count(interface_key::CUSTOM_CTRL_A)) { cursor_pos = 0; } - else if(events->count(interface_key::CUSTOM_CTRL_E)) + else if (events->count(interface_key::CUSTOM_CTRL_E)) { cursor_pos = entry.size(); } - else if(events->count(interface_key::CURSOR_UP)) + else if (events->count(interface_key::CURSOR_UP)) { history_idx--; if (history_idx < 0) @@ -296,7 +300,7 @@ void viewscreen_commandpromptst::feed(std::set *events) entry = get_entry(); cursor_pos = entry.size(); } - else if(events->count(interface_key::CURSOR_DOWN)) + else if (events->count(interface_key::CURSOR_DOWN)) { if (size_t(history_idx) < command_history.size() - 1) { @@ -321,8 +325,8 @@ command_result show_prompt(color_ostream &out, std::vector & param return CR_OK; } std::string params; - for(size_t i=0;i(params), plugin_self); return CR_OK; } @@ -330,21 +334,23 @@ bool hotkey_allow_all(df::viewscreen *top) { return true; } -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "command-prompt","Shows a command prompt on window.",show_prompt,hotkey_allow_all, - "command-prompt [entry] - shows a cmd prompt in df window. Entry is used for default prefix (e.g. ':lua')" + "command-prompt", "Shows a command prompt on window.", + show_prompt, hotkey_allow_all, + "command-prompt [entry] - shows a cmd prompt in df window." + " Entry is used for default prefix (e.g. ':lua')" )); return CR_OK; } -DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event e) +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event e) { return CR_OK; } -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +DFhackCExport command_result plugin_shutdown(color_ostream &out) { return CR_OK; } From 5d2739eee0a770b9da6723325011e5ab7bafcf78 Mon Sep 17 00:00:00 2001 From: Tim Siegel Date: Thu, 5 May 2022 14:52:33 -0400 Subject: [PATCH 038/854] [command-prompt] word-wrap response text Fixes #2079 --- plugins/command-prompt.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/plugins/command-prompt.cpp b/plugins/command-prompt.cpp index 0bfe31dc9..ba2fe0e87 100644 --- a/plugins/command-prompt.cpp +++ b/plugins/command-prompt.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -11,6 +12,7 @@ #include #include +#include #include #include @@ -159,13 +161,18 @@ void viewscreen_commandpromptst::render() parent->render(); if (is_response) { - auto it = responses.begin(); - for (int i = 0; i < dim.y && it != responses.end(); i++, it++) + int y = 0; + for (auto &response : responses) { - Screen::fillRect(Screen::Pen(' ', 7, 0), 0, i, dim.x, i); - std::string cur_line = it->second; - Screen::paintString(Screen::Pen(' ', it->first, 0), 0, i, - cur_line.substr(0, cur_line.size() - 1)); + std::vector lines; + word_wrap(&lines, response.second, dim.x); + for (auto &line : lines) + { + Screen::fillRect(Screen::Pen(' ', 7, 0), 0, y, dim.x, y); + Screen::paintString(Screen::Pen(' ', response.first, 0), 0, y, line); + if (++y >= dim.y) + return; + } } } else From 1203274e9f8a78f5c9dd59ca616f4afce795da3f Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 9 May 2022 23:03:07 -0400 Subject: [PATCH 039/854] Update Authors.rst This time with missing scripts and df-structures authors since 0.47.04-r1 Just one: DFHack/scripts#339 --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 38fe19862..afb0a3f14 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -32,6 +32,7 @@ billw2012 billw2012 BrickViking brickviking brndd brndd burneddi Caldfir caldfir +Cameron Ewell Ozzatron Carter Bray Qartar Chris Dombroski cdombroski Chris Parsons chrismdp From f9cc79fb8de0f491297775981ecf5069214ab014 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 10 May 2022 00:57:56 -0400 Subject: [PATCH 040/854] pre-commit: decrease autoupdate interval to monthly --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 26d135ca2..336bda270 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,6 @@ ci: autofix_prs: false + autoupdate_schedule: monthly repos: # shared across repos: - repo: https://github.com/pre-commit/pre-commit-hooks From 15a11d1a39e9f5b6e9af50ef5e3bb58693fe39dd Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 10 May 2022 07:22:46 +0000 Subject: [PATCH 041/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 7ec5d8699..8b2d2b737 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 7ec5d86996269df5e01d64ea5bae67d0c29afd77 +Subproject commit 8b2d2b7379f4d5658573c9c4ff6d383e032f3a5c diff --git a/scripts b/scripts index 64626c814..86c24157b 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 64626c81481a2f938f071b7347d07c10312b223d +Subproject commit 86c24157b33de06bea090d35d29adff68cdbd2a6 From 7439678214160390fbddcc318159bd159365fcbd Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 10 May 2022 11:14:30 -0700 Subject: [PATCH 042/854] tombstone devel/unforbidall script --- docs/Removed.rst | 8 ++++++++ scripts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/Removed.rst b/docs/Removed.rst index 5d68b2133..54e83a6fc 100644 --- a/docs/Removed.rst +++ b/docs/Removed.rst @@ -10,6 +10,14 @@ work (e.g. links from the `changelog`). :local: :depth: 1 +.. _devel/unforbidall: + +devel/unforbidall +================= + +Replaced by the `unforbid` script. Run ``unforbid all --quiet`` to match the +behavior of the original ``devel/unforbidall`` script. + .. _digfort: digfort diff --git a/scripts b/scripts index 86c24157b..ec09032b7 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 86c24157b33de06bea090d35d29adff68cdbd2a6 +Subproject commit ec09032b7a44d531c8f5b91cff11632238a8c469 From 8696f72f7736d250e1b2985ab5321fe3cf0bd677 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 11 May 2022 01:06:47 -0400 Subject: [PATCH 043/854] Fix script-docs.py extension check The check previously matched any filename ending in `lua`, not `.lua`. This caused failures in my fork because I had a branch ending in `-lua`, which created a file of that name in `.git/refs` that was not a valid Lua script. For extra good measure, anything under `.git` is ignored now as well. --- ci/script-docs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/script-docs.py b/ci/script-docs.py index 71d7f37b2..7ef287f8a 100755 --- a/ci/script-docs.py +++ b/ci/script-docs.py @@ -81,11 +81,11 @@ def check_file(fname): def main(): """Check that all DFHack scripts include documentation""" err = 0 - exclude = set(['internal', 'test']) + exclude = {'.git', 'internal', 'test'} for root, dirs, files in os.walk(SCRIPT_PATH, topdown=True): dirs[:] = [d for d in dirs if d not in exclude] for f in files: - if f[-3:] in {'.rb', 'lua'}: + if f.split('.')[-1] in {'rb', 'lua'}: err += check_file(join(root, f)) return err From baab258774a478c91f157775dc1e739bd7854658 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 11 May 2022 07:23:49 +0000 Subject: [PATCH 044/854] Auto-update submodules scripts: master depends/libzip: dfhack depends/libexpat: dfhack depends/xlsxio: dfhack --- depends/libexpat | 2 +- depends/libzip | 2 +- depends/xlsxio | 2 +- scripts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/depends/libexpat b/depends/libexpat index 3c0f2e86c..ca5c29a22 160000 --- a/depends/libexpat +++ b/depends/libexpat @@ -1 +1 @@ -Subproject commit 3c0f2e86ce4e7a3a3b30e765087d02a68bba7e6f +Subproject commit ca5c29a22cba22befd8293b4adf76fa1937e8651 diff --git a/depends/libzip b/depends/libzip index da0d18ae5..081249cce 160000 --- a/depends/libzip +++ b/depends/libzip @@ -1 +1 @@ -Subproject commit da0d18ae59ef2699013316b703cdc93809414c93 +Subproject commit 081249cceb59adc857a72d67e60c32047680f787 diff --git a/depends/xlsxio b/depends/xlsxio index 4056226fe..ab8fd7f3e 160000 --- a/depends/xlsxio +++ b/depends/xlsxio @@ -1 +1 @@ -Subproject commit 4056226fe0df6bff4593ee2353cca07c2b7f327e +Subproject commit ab8fd7f3e9df457e8bc1b5cb31b78d57df0ac5a4 diff --git a/scripts b/scripts index ec09032b7..489b8588b 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ec09032b7a44d531c8f5b91cff11632238a8c469 +Subproject commit 489b8588b2bec4c2d29fe35c0fc3a15d3a71f7eb From 2c6e450ac991114de75f143b7e42f7aeee81c789 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 10 May 2022 15:08:55 -0700 Subject: [PATCH 045/854] update the list of submodules to autoupdate --- ci/update-submodules.manifest | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/update-submodules.manifest b/ci/update-submodules.manifest index 4bd383018..124f6db7c 100644 --- a/ci/update-submodules.manifest +++ b/ci/update-submodules.manifest @@ -1,7 +1,9 @@ library/xml master scripts master plugins/stonesense master +plugins/isoworld master depends/libzip dfhack depends/libexpat dfhack depends/xlsxio dfhack depends/luacov dfhack +depends/jsoncpp dfhack From a3a0631c41532533ab6cd7e0b74aef1c239ea69b Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 11 May 2022 22:18:52 -0700 Subject: [PATCH 046/854] fix typo in ci/update-submodules.manifest --- ci/update-submodules.manifest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/update-submodules.manifest b/ci/update-submodules.manifest index 124f6db7c..fee90617c 100644 --- a/ci/update-submodules.manifest +++ b/ci/update-submodules.manifest @@ -6,4 +6,4 @@ depends/libzip dfhack depends/libexpat dfhack depends/xlsxio dfhack depends/luacov dfhack -depends/jsoncpp dfhack +depends/jsoncpp-sub dfhack From bf8eaef6bc65849fc83ed0d215213ad6fca38be9 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 12 May 2022 05:21:01 +0000 Subject: [PATCH 047/854] Auto-update submodules library/xml: master plugins/isoworld: master depends/libexpat: dfhack depends/jsoncpp-sub: dfhack --- depends/jsoncpp-sub | 2 +- depends/libexpat | 2 +- library/xml | 2 +- plugins/isoworld | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/depends/jsoncpp-sub b/depends/jsoncpp-sub index ddabf50f7..632044ad9 160000 --- a/depends/jsoncpp-sub +++ b/depends/jsoncpp-sub @@ -1 +1 @@ -Subproject commit ddabf50f72cf369bf652a95c4d9fe31a1865a781 +Subproject commit 632044ad956b9ca55703d98d852bc815077f6afc diff --git a/depends/libexpat b/depends/libexpat index ca5c29a22..3e877cbb3 160000 --- a/depends/libexpat +++ b/depends/libexpat @@ -1 +1 @@ -Subproject commit ca5c29a22cba22befd8293b4adf76fa1937e8651 +Subproject commit 3e877cbb3c9bc8f22946053e70490d2e5431f4d5 diff --git a/library/xml b/library/xml index 8b2d2b737..59075f42b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 8b2d2b7379f4d5658573c9c4ff6d383e032f3a5c +Subproject commit 59075f42bbc77c354b5f815c5c1cce5bf48e76a5 diff --git a/plugins/isoworld b/plugins/isoworld index e3c49ab01..8f500def2 160000 --- a/plugins/isoworld +++ b/plugins/isoworld @@ -1 +1 @@ -Subproject commit e3c49ab017da2dcbeaadccd10e56d07d8f03b4ca +Subproject commit 8f500def2bf8cd95f7b61ded192e9a26651ede4a From 08a39400de0b5a09cdfa1519378d59da787e9b14 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 12 May 2022 05:33:14 +0000 Subject: [PATCH 048/854] Auto-update submodules depends/jsoncpp-sub: dfhack --- depends/jsoncpp-sub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/jsoncpp-sub b/depends/jsoncpp-sub index 632044ad9..ddabf50f7 160000 --- a/depends/jsoncpp-sub +++ b/depends/jsoncpp-sub @@ -1 +1 @@ -Subproject commit 632044ad956b9ca55703d98d852bc815077f6afc +Subproject commit ddabf50f72cf369bf652a95c4d9fe31a1865a781 From b2aa2a9b31e74eb1038c9db04ae54072b0b77644 Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 11 May 2022 22:40:33 -0700 Subject: [PATCH 049/854] move isoworld to pull from the new dfhack branch --- ci/update-submodules.manifest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/update-submodules.manifest b/ci/update-submodules.manifest index fee90617c..e97cae6f3 100644 --- a/ci/update-submodules.manifest +++ b/ci/update-submodules.manifest @@ -1,7 +1,7 @@ library/xml master scripts master plugins/stonesense master -plugins/isoworld master +plugins/isoworld dfhack depends/libzip dfhack depends/libexpat dfhack depends/xlsxio dfhack From 4d08a49afb0015cdc11ca6417b6cf4006405eefa Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 12 May 2022 05:41:20 +0000 Subject: [PATCH 050/854] Auto-update submodules plugins/isoworld: dfhack --- plugins/isoworld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/isoworld b/plugins/isoworld index 8f500def2..e3c49ab01 160000 --- a/plugins/isoworld +++ b/plugins/isoworld @@ -1 +1 @@ -Subproject commit 8f500def2bf8cd95f7b61ded192e9a26651ede4a +Subproject commit e3c49ab017da2dcbeaadccd10e56d07d8f03b4ca From 8f6522899bbdd3f0f2355d29a5dba15de55f89a2 Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 13 May 2022 13:52:43 -0700 Subject: [PATCH 051/854] Upgrade jsoncpp to 1.9.5 (#2144) * test jsoncpp upgrade * use new json library target name * don't remap the output dirs * undo warnings at the source * set new defaults for jsoncpp * fix typo in new options * fix signed comparison mismatch warning * address random(?) compile failures saying our std::atomic is not initialized in Debug.cpp --- depends/CMakeLists.txt | 7 ++++--- depends/jsoncpp-sub | 2 +- library/CMakeLists.txt | 4 ++-- library/Debug.cpp | 2 +- plugins/CMakeLists.txt | 6 +++--- plugins/autolabor.cpp | 2 +- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/depends/CMakeLists.txt b/depends/CMakeLists.txt index 0fcb4ca3c..fce25a65e 100644 --- a/depends/CMakeLists.txt +++ b/depends/CMakeLists.txt @@ -12,10 +12,11 @@ endif() add_subdirectory(tthread) option(JSONCPP_WITH_TESTS "Compile and (for jsoncpp_check) run JsonCpp test executables" OFF) option(JSONCPP_WITH_POST_BUILD_UNITTEST "Automatically run unit-tests as a post build step" OFF) +option(JSONCPP_BUILD_SHARED_LIBS "Build jsoncpp_lib as a shared library." OFF) +option(JSONCPP_BUILD_OBJECT_LIBS "Build jsoncpp_lib as a object library." OFF) +option(JSONCPP_WITH_CMAKE_PACKAGE "Generate and install cmake package files" OFF) + add_subdirectory(jsoncpp-sub EXCLUDE_FROM_ALL) -if(UNIX) - set_target_properties(jsoncpp_lib_static PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations") -endif() # build clsocket static and only as a dependency. Setting those options here overrides its own default settings. option(CLSOCKET_SHARED "Build clsocket lib as shared." OFF) option(CLSOCKET_DEP_ONLY "Build for use inside other CMake projects as dependency." ON) diff --git a/depends/jsoncpp-sub b/depends/jsoncpp-sub index ddabf50f7..ba5eac541 160000 --- a/depends/jsoncpp-sub +++ b/depends/jsoncpp-sub @@ -1 +1 @@ -Subproject commit ddabf50f72cf369bf652a95c4d9fe31a1865a781 +Subproject commit ba5eac54136064af94ab4a923ac110d7534d4f83 diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 4ec165308..9e7bf8590 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -415,10 +415,10 @@ if(APPLE) set_target_properties(dfhack PROPERTIES SOVERSION 1.0.0) endif() -target_link_libraries(dfhack protobuf-lite clsocket lua jsoncpp_lib_static dfhack-version ${PROJECT_LIBS}) +target_link_libraries(dfhack protobuf-lite clsocket lua jsoncpp_static dfhack-version ${PROJECT_LIBS}) set_target_properties(dfhack PROPERTIES INTERFACE_LINK_LIBRARIES "") -target_link_libraries(dfhack-client protobuf-lite clsocket jsoncpp_lib_static) +target_link_libraries(dfhack-client protobuf-lite clsocket jsoncpp_static) target_link_libraries(dfhack-run dfhack-client) if(APPLE) diff --git a/library/Debug.cpp b/library/Debug.cpp index 7ac981d30..9b13af168 100644 --- a/library/Debug.cpp +++ b/library/Debug.cpp @@ -198,7 +198,7 @@ DebugCategory::cstring_ref DebugCategory::plugin() const noexcept //! standards only provide runtime checks if an atomic type is lock free struct failIfEnumAtomicIsNotLockFree { failIfEnumAtomicIsNotLockFree() { - std::atomic test; + std::atomic test(DebugCategory::LINFO); if (test.is_lock_free()) return; std::cerr << __FILE__ << ':' << __LINE__ diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index b8cd25860..ed9404b8e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -87,7 +87,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(autoclothing autoclothing.cpp) dfhack_plugin(autodump autodump.cpp) dfhack_plugin(autofarm autofarm.cpp) - dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_lib_static) + dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_static) dfhack_plugin(autohauler autohauler.cpp) dfhack_plugin(autolabor autolabor.cpp) dfhack_plugin(automaterial automaterial.cpp LINK_LIBRARIES lua) @@ -109,7 +109,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(cursecheck cursecheck.cpp) dfhack_plugin(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) dfhack_plugin(deramp deramp.cpp) - dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_lib_static) + dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_static) dfhack_plugin(dig dig.cpp) dfhack_plugin(dig-now dig-now.cpp LINK_LIBRARIES lua) dfhack_plugin(digFlood digFlood.cpp) @@ -143,7 +143,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(mode mode.cpp) dfhack_plugin(mousequery mousequery.cpp) dfhack_plugin(nestboxes nestboxes.cpp) - dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_lib_static) + dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static) dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua) dfhack_plugin(petcapRemover petcapRemover.cpp) dfhack_plugin(plants plants.cpp) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index d6921f1ac..ba116c1c5 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -873,7 +873,7 @@ static void assign_labor(unit_labor::unit_labor labor, } int pool = labor_infos[labor].talent_pool(); - if (pool < 200 && candidates.size() > 1 && abs(pool) < candidates.size()) + if (pool < 200 && candidates.size() > 1 && size_t(abs(pool)) < candidates.size()) { // Sort in descending order std::sort(candidates.begin(), candidates.end(), [&](const int lhs, const int rhs) -> bool { From 1b426efdb93b362aa46fa694fc0936505f9bcc7c Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 13 May 2022 13:40:17 -0700 Subject: [PATCH 052/854] reduce spurious warnings from libzip --- depends/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/depends/CMakeLists.txt b/depends/CMakeLists.txt index fce25a65e..405a9555e 100644 --- a/depends/CMakeLists.txt +++ b/depends/CMakeLists.txt @@ -38,6 +38,7 @@ if(UNIX) set_target_properties(expat PROPERTIES COMPILE_FLAGS "-Wno-maybe-uninitialized") endif() +set(CMAKE_REQUIRED_QUIET ON) set(LIBZIP_BUILD_DOC OFF CACHE BOOL "") set(LIBZIP_BUILD_EXAMPLES OFF CACHE BOOL "") set(LIBZIP_BUILD_REGRESS OFF CACHE BOOL "") From b489a8584f37071cb562d3b9f008a219e48ba318 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 29 Apr 2022 10:00:26 -0700 Subject: [PATCH 053/854] add more prepared meals logic also remove material matchers for jugs and pots. we only care that we have enough jugs and pots, not that they are rock --- data/examples/orders/basic.json | 131 +++++++++++++++++++------------- 1 file changed, 80 insertions(+), 51 deletions(-) diff --git a/data/examples/orders/basic.json b/data/examples/orders/basic.json index fb4668e81..dfb47998e 100644 --- a/data/examples/orders/basic.json +++ b/data/examples/orders/basic.json @@ -1,12 +1,34 @@ [ { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", + "amount_left" : 150, + "amount_total" : 150, + "frequency" : "Monthly", "id" : 0, "is_active" : false, "is_validated" : false, "item_conditions" : + [ + { + "condition" : "LessThan", + "flags" : + [ + "unrotten" + ], + "item_type" : "FOOD", + "value" : 400 + } + ], + "job" : "PrepareMeal", + "meal_ingredients" : 2 + }, + { + "amount_left" : 10, + "amount_total" : 10, + "frequency" : "Daily", + "id" : 1, + "is_active" : false, + "is_validated" : false, + "item_conditions" : [ { "condition" : "AtLeast", @@ -25,7 +47,7 @@ "unrotten", "cookable" ], - "value" : 15 + "value" : 1000 }, { "condition" : "AtMost", @@ -35,6 +57,15 @@ ], "item_type" : "FOOD", "value" : 3500 + }, + { + "condition" : "AtLeast", + "flags" : + [ + "unrotten" + ], + "item_type" : "FOOD", + "value" : 400 } ], "job" : "PrepareMeal", @@ -44,7 +75,7 @@ "amount_left" : 2, "amount_total" : 2, "frequency" : "Daily", - "id" : 1, + "id" : 2, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -81,7 +112,7 @@ "amount_left" : 2, "amount_total" : 2, "frequency" : "Daily", - "id" : 2, + "id" : 3, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -118,7 +149,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 3, + "id" : 4, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -139,7 +170,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 4, + "id" : 5, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -174,7 +205,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 5, + "id" : 6, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -206,7 +237,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 6, + "id" : 7, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -238,7 +269,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 7, + "id" : 8, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -260,7 +291,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 8, + "id" : 9, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -293,7 +324,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 9, + "id" : 10, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -324,7 +355,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 10, + "id" : 11, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -357,7 +388,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 11, + "id" : 12, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -397,7 +428,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 12, + "id" : 13, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -423,7 +454,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 13, + "id" : 14, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -458,7 +489,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 14, + "id" : 15, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -493,7 +524,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 15, + "id" : 16, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -528,7 +559,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 16, + "id" : 17, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -559,7 +590,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 17, + "id" : 18, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -589,7 +620,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 18, + "id" : 19, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -613,7 +644,6 @@ ], "item_subtype" : "ITEM_TOOL_LARGE_POT", "item_type" : "TOOL", - "material" : "INORGANIC", "value" : 25 } ], @@ -625,7 +655,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 19, + "id" : 20, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -649,7 +679,6 @@ ], "item_subtype" : "ITEM_TOOL_JUG", "item_type" : "TOOL", - "material" : "INORGANIC", "value" : 10 } ], @@ -661,7 +690,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 20, + "id" : 21, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -690,7 +719,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 21, + "id" : 22, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -718,7 +747,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 22, + "id" : 23, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -746,7 +775,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 23, + "id" : 24, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -776,7 +805,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 24, + "id" : 25, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -806,7 +835,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 25, + "id" : 26, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -835,7 +864,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 26, + "id" : 27, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -866,7 +895,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 27, + "id" : 28, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -902,7 +931,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 28, + "id" : 29, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -938,7 +967,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 29, + "id" : 30, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -974,7 +1003,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 30, + "id" : 31, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -993,7 +1022,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 31, + "id" : 32, "is_active" : false, "is_validated" : true, "item_conditions" : @@ -1019,7 +1048,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 32, + "id" : 33, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1041,7 +1070,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 33, + "id" : 34, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1067,7 +1096,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 34, + "id" : 35, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1093,7 +1122,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 35, + "id" : 36, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1119,7 +1148,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 36, + "id" : 37, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1152,7 +1181,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 37, + "id" : 38, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1191,7 +1220,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 38, + "id" : 39, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1225,7 +1254,7 @@ "amount_left" : 4, "amount_total" : 4, "frequency" : "Daily", - "id" : 39, + "id" : 40, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1248,7 +1277,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 40, + "id" : 41, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1272,7 +1301,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 41, + "id" : 42, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1306,7 +1335,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 42, + "id" : 43, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1335,7 +1364,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 43, + "id" : 44, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1364,7 +1393,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 44, + "id" : 45, "is_active" : false, "is_validated" : true, "item_conditions" : @@ -1395,7 +1424,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 45, + "id" : 46, "is_active" : false, "is_validated" : false, "item_conditions" : From e4f951b01d7530d93d0af6131c67f3ef42b319e6 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 1 May 2022 11:54:05 -0700 Subject: [PATCH 054/854] make both easy and lavish meals --- data/examples/orders/basic.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/examples/orders/basic.json b/data/examples/orders/basic.json index dfb47998e..e8dee1680 100644 --- a/data/examples/orders/basic.json +++ b/data/examples/orders/basic.json @@ -47,7 +47,7 @@ "unrotten", "cookable" ], - "value" : 1000 + "value" : 500 }, { "condition" : "AtMost", From d6df928d6970c287e6de6f5fd374a9d160dc5db4 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 13 May 2022 13:58:36 -0700 Subject: [PATCH 055/854] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 80c52e046..5ceb91dd1 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -40,6 +40,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes ## Misc Improvements +- `dfhack-examples-guide`: refine food preparation orders and fix conditions for making jugs and pots in the ``basic`` manager orders ## Documentation From ac8ac9608053b05471998df153be57c29b5aa42f Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 13 May 2022 14:22:05 -0700 Subject: [PATCH 056/854] fix warning on windows about possible loss of data --- depends/lua/src/lapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/lua/src/lapi.c b/depends/lua/src/lapi.c index 711895b39..aa01148ab 100644 --- a/depends/lua/src/lapi.c +++ b/depends/lua/src/lapi.c @@ -395,7 +395,7 @@ LUA_API size_t lua_rawlen (lua_State *L, int idx) { case LUA_TSHRSTR: return tsvalue(o)->shrlen; case LUA_TLNGSTR: return tsvalue(o)->u.lnglen; case LUA_TUSERDATA: return uvalue(o)->len; - case LUA_TTABLE: return luaH_getn(hvalue(o)); + case LUA_TTABLE: return size_t(luaH_getn(hvalue(o))); default: return 0; } } From dfd3a39f0ecd7eaf96b71b362d103c5285264fde Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 14 May 2022 07:23:55 +0000 Subject: [PATCH 057/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 489b8588b..e062237ee 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 489b8588b2bec4c2d29fe35c0fc3a15d3a71f7eb +Subproject commit e062237ee03a22d0d8b88b725ba3712e649f1bf6 From 785fe9aa3a7eda8108639f632e0979c61c15d8f8 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 14 May 2022 00:55:11 -0700 Subject: [PATCH 058/854] fix "info" level described as "error" level --- library/include/Debug.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/include/Debug.h b/library/include/Debug.h index 63811a51e..4cad178dc 100644 --- a/library/include/Debug.h +++ b/library/include/Debug.h @@ -326,7 +326,7 @@ public: DFHack::DebugCategory::LDEBUG, ## __VA_ARGS__) /*! - * Open a line for error level debug output if enabled + * Open a line for info level debug output if enabled * * Important debug messages when some rarely changed state changes. Example * would be when a debug category filtering level changes. From 0da881d5eb07a6e96b8109e8ec29a6ae49dd5d8b Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sun, 15 May 2022 07:17:46 +0000 Subject: [PATCH 059/854] Auto-update submodules plugins/isoworld: dfhack --- plugins/isoworld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/isoworld b/plugins/isoworld index e3c49ab01..3630c816d 160000 --- a/plugins/isoworld +++ b/plugins/isoworld @@ -1 +1 @@ -Subproject commit e3c49ab017da2dcbeaadccd10e56d07d8f03b4ca +Subproject commit 3630c816df6962b4594d46d1ae75974c36c11629 From 47b87a5ac18d3e939a22d39d1339f115c1bf357b Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 17 May 2022 07:18:04 +0000 Subject: [PATCH 060/854] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 59075f42b..a59495e8f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 59075f42bbc77c354b5f815c5c1cce5bf48e76a5 +Subproject commit a59495e8f72115909772e6df20a7b9dec272f14c From 53609db1f9b59f3d7c3a4eb43b315e4927418e00 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 18 May 2022 14:04:56 -0700 Subject: [PATCH 061/854] let CycleHotkeyLabels take numeric initial_options even if all of the option values are non-numeric --- docs/changelog.txt | 1 + library/lua/gui/widgets.lua | 5 +++++ test/library/gui/widgets.lua | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 test/library/gui/widgets.lua diff --git a/docs/changelog.txt b/docs/changelog.txt index 5ceb91dd1..14fb4db22 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -38,6 +38,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## New Tweaks ## Fixes +- ``widgets.CycleHotkeyLabel``: allow initial option values to be specified as an index instead of an option value ## Misc Improvements - `dfhack-examples-guide`: refine food preparation orders and fix conditions for making jugs and pots in the ``basic`` manager orders diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index b8782bd04..6a13d2af5 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -637,6 +637,11 @@ function CycleHotkeyLabel:init() break end end + if not self.option_idx then + if self.options[self.initial_option] then + self.option_idx = self.initial_option + end + end if not self.option_idx then error(('cannot find option with value or index: "%s"') :format(self.initial_option)) diff --git a/test/library/gui/widgets.lua b/test/library/gui/widgets.lua new file mode 100644 index 000000000..1eed30e4f --- /dev/null +++ b/test/library/gui/widgets.lua @@ -0,0 +1,18 @@ +local widgets = require('gui.widgets') + +function test.togglehotkeylabel() + local toggle = widgets.ToggleHotkeyLabel{} + expect.true_(toggle:getOptionValue()) + toggle:cycle() + expect.false_(toggle:getOptionValue()) + toggle:cycle() + expect.true_(toggle:getOptionValue()) +end + +function test.togglehotkeylabel_default_value() + local toggle = widgets.ToggleHotkeyLabel{initial_option=2} + expect.false_(toggle:getOptionValue()) + + toggle = widgets.ToggleHotkeyLabel{initial_option=false} + expect.false_(toggle:getOptionValue()) +end From 18628ff5b8351894ac00aa66bfbbe7d744bc1f0b Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 18 May 2022 16:35:06 -0700 Subject: [PATCH 062/854] make key_sep more configurable and add more configuration to HotkeyLabel and EditField to take advantage of it --- docs/Lua API.rst | 9 +++++++-- docs/changelog.txt | 3 +++ library/lua/gui/widgets.lua | 34 ++++++++++++++++++++++------------ 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index c0dec4b6e..04257b49d 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3832,6 +3832,7 @@ Subclass of Widget; implements a simple edit field. Attributes: +:label_text: The optional text label displayed before the editable text. :text: The current contents of the field. :text_pen: The pen to draw the text with. :on_char: Input validation callback; used as ``on_char(new_char,text)``. @@ -3839,6 +3840,8 @@ Attributes: :on_change: Change notification callback; used as ``on_change(new_text,old_text)``. :on_submit: Enter key callback; if set the field will handle the key and call ``on_submit(text)``. :key: If specified, the field is disabled until this key is pressed. Must be given as a string. +:key_sep: If specified, will be used to customize how the activation key is + displayed. See ``token.key_sep`` in the ``Label`` documentation below. Label class ----------- @@ -3907,8 +3910,8 @@ containing newlines, or a table with the following possible fields: * ``token.key_sep = '...'`` Specifies the separator to place between the keybinding label produced - by ``token.key``, and the main text of the token. If the separator is - '()', the token is formatted as ``text..' ('..binding..')'``. Otherwise + by ``token.key``, and the main text of the token. If the separator starts with + '()', the token is formatted as ``text..' ('..binding..sep:sub(2)``. Otherwise it is simply ``binding..sep..text``. * ``token.enabled``, ``token.disabled`` @@ -3999,6 +4002,8 @@ a hotkey. It has the following attributes: :key: The hotkey keycode to display, e.g. ``'CUSTOM_A'``. +:key_sep: If specified, will be used to customize how the activation key is + displayed. See ``token.key_sep`` in the ``Label`` documentation. :label: The string (or a function that returns a string) to display after the hotkey. :on_activate: If specified, it is the callback that will be called whenever diff --git a/docs/changelog.txt b/docs/changelog.txt index 14fb4db22..325070526 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -48,6 +48,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API ## Lua +- ``widgets.HotkeyLabel``: the ``key_sep`` string is now configurable +- ``widgets.EditField``: the ``key_sep`` string is now configurable +- ``widgets.EditField``: can now display an optional string label in addition to the activation key # 0.47.05-r5 diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 6a13d2af5..21e03370b 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -178,14 +178,27 @@ end EditField = defclass(EditField, Widget) EditField.ATTRS{ + label_text = DEFAULT_NIL, text = '', text_pen = DEFAULT_NIL, on_char = DEFAULT_NIL, on_change = DEFAULT_NIL, on_submit = DEFAULT_NIL, key = DEFAULT_NIL, + key_sep = DEFAULT_NIL, } +function EditField:init() + self:addviews{HotkeyLabel{frame={t=0,l=0}, + key=self.key, + key_sep=self.key_sep, + label=self.label_text}} +end + +function EditField:postUpdateLayout() + self.text_offset = self.subviews[1]:getTextWidth() +end + function EditField:onRenderBody(dc) dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0) @@ -194,16 +207,11 @@ function EditField:onRenderBody(dc) cursor = ' ' end local txt = self.text .. cursor - local dx = dc.x - if self.key then - dc:key_string(self.key, '') - end - dx = dc.x - dx - local max_width = dc.width - dx + local max_width = dc.width - self.text_offset if #txt > max_width then txt = string.char(27)..string.sub(txt, #txt-max_width+2) end - dc:string(txt) + dc:advance(self.text_offset):string(txt) end function EditField:onInput(keys) @@ -359,12 +367,13 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) x = x + #keystr - if sep == '()' then + if sep:startswith('()') then if dc then dc:string(text) - dc:string(' ('):string(keystr,keypen):string(')') + dc:string(' ('):string(keystr,keypen) + dc:string(sep:sub(2)) end - x = x + 3 + x = x + 1 + #sep else if dc then dc:string(keystr,keypen):string(sep):string(text) @@ -605,13 +614,14 @@ HotkeyLabel = defclass(HotkeyLabel, Label) HotkeyLabel.ATTRS{ key=DEFAULT_NIL, + key_sep=': ', label=DEFAULT_NIL, on_activate=DEFAULT_NIL, } function HotkeyLabel:init() - self:setText{{key=self.key, key_sep=': ', text=self.label, - on_activate=self.on_activate}} + self:setText{{key=self.key, key_sep=self.key_sep, text=self.label, + on_activate=self.on_activate}} end ---------------------- From 8b1070b553428c1b1c2eb1afe493b669f50f6e85 Mon Sep 17 00:00:00 2001 From: Myk Date: Thu, 19 May 2022 15:27:08 -0700 Subject: [PATCH 063/854] tombstone deteriorate*rb scripts (#2157) * tombstone deteriorate*rb scripts * update to scripts head so we can build docs * fix anchors --- docs/Removed.rst | 21 +++++++++++++++++++++ scripts | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/Removed.rst b/docs/Removed.rst index 54e83a6fc..e94b04cf8 100644 --- a/docs/Removed.rst +++ b/docs/Removed.rst @@ -18,6 +18,27 @@ devel/unforbidall Replaced by the `unforbid` script. Run ``unforbid all --quiet`` to match the behavior of the original ``devel/unforbidall`` script. +.. _deteriorateclothes: + +deteriorateclothes +================== +Replaced by the new combined `deteriorate` script. Run +``deteriorate --types=clothes``. + +.. _deterioratecorpses: + +deterioratecorpses +================== +Replaced by the new combined `deteriorate` script. Run +``deteriorate --types=corpses``. + +.. _deterioratefood: + +deterioratefood +=============== +Replaced by the new combined `deteriorate` script. Run +``deteriorate --types=food``. + .. _digfort: digfort diff --git a/scripts b/scripts index e062237ee..99489eea1 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit e062237ee03a22d0d8b88b725ba3712e649f1bf6 +Subproject commit 99489eea1eadbc175740043f7c0b988a26511d71 From 81edd716d0cb5117d17423ca47a9c63d08fad5d9 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 19 May 2022 16:36:22 -0700 Subject: [PATCH 064/854] update scripts HEAD with test fixes --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 99489eea1..219dbb190 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 99489eea1eadbc175740043f7c0b988a26511d71 +Subproject commit 219dbb190dba4440df5f11d0e6a9b6556f919709 From c4febc789a36e48368fc4ddf3ec7dcdbe87311f7 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 21 May 2022 07:17:35 +0000 Subject: [PATCH 065/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 219dbb190..05d46b32a 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 219dbb190dba4440df5f11d0e6a9b6556f919709 +Subproject commit 05d46b32a3aff4f5f98534fdccfbf9ae88dd31a3 From 77d045488bb72b0767766d094a97e5ccf1b66fa2 Mon Sep 17 00:00:00 2001 From: Myk Date: Mon, 23 May 2022 21:17:52 -0700 Subject: [PATCH 066/854] Confirmation dialog for removing manager orders (#2163) --- docs/changelog.txt | 1 + plugins/confirm.cpp | 2 ++ plugins/lua/confirm.lua | 8 ++++++++ 3 files changed, 11 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 325070526..b2ef0e90a 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,6 +41,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``widgets.CycleHotkeyLabel``: allow initial option values to be specified as an index instead of an option value ## Misc Improvements +- `confirm`: added a confirmation dialog for removing manager orders - `dfhack-examples-guide`: refine food preparation orders and fix conditions for making jugs and pots in the ``basic`` manager orders ## Documentation diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index a57ec45dd..6a2798cb4 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -18,6 +18,7 @@ #include "df/general_ref.h" #include "df/general_ref_contained_in_itemst.h" #include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_jobmanagementst.h" #include "df/viewscreen_justicest.h" #include "df/viewscreen_layer_militaryst.h" #include "df/viewscreen_locationsst.h" @@ -481,6 +482,7 @@ DEFINE_CONFIRMATION(note_delete, viewscreen_dwarfmodest); DEFINE_CONFIRMATION(route_delete, viewscreen_dwarfmodest); DEFINE_CONFIRMATION(location_retire, viewscreen_locationsst); DEFINE_CONFIRMATION(convict, viewscreen_justicest); +DEFINE_CONFIRMATION(order_remove, viewscreen_jobmanagementst); DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { diff --git a/plugins/lua/confirm.lua b/plugins/lua/confirm.lua index 13b28962a..e36a4fb86 100644 --- a/plugins/lua/confirm.lua +++ b/plugins/lua/confirm.lua @@ -219,6 +219,14 @@ function convict.get_message() "This action is irreversible." end +order_remove = defconf('order-remove') +function order_remove.intercept_key(key) + return key == keys.MANAGER_REMOVE and + not screen.in_max_workshops +end +order_remove.title = "Remove manager order" +order_remove.message = "Are you sure you want to remove this order?" + -- End of confirmation definitions function check() From 56b301164fde481b69ba7909646781e436177286 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 25 May 2022 07:18:15 +0000 Subject: [PATCH 067/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index a59495e8f..1dfe6c5ab 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a59495e8f72115909772e6df20a7b9dec272f14c +Subproject commit 1dfe6c5ab9887507cdcdebdd9390352fe0bba2dd diff --git a/scripts b/scripts index 05d46b32a..b1bfe27aa 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 05d46b32a3aff4f5f98534fdccfbf9ae88dd31a3 +Subproject commit b1bfe27aa95ceb6c645f40c412c34d92c74b4bb0 From 1f58896054375e39d096c727d779f3365cd63fa5 Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 25 May 2022 22:49:09 -0700 Subject: [PATCH 068/854] Clarify text and fix typos in the readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 15f13dbc9..fa6c343e1 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ DFHack is a Dwarf Fortress memory access library, distributed with scripts and plugins implementing a wide variety of useful functions and tools. -The full documentation [is available online here](https://dfhack.readthedocs.org), -from the README.html page in the DFHack distribution, or as raw text in the `./docs` folder. +The full documentation [is available online here](https://dfhack.readthedocs.org). +It is also accessible via the README.html page in the DFHack distribution or as raw text in the `./docs` folder. If you're an end-user, modder, or interested in contributing to DFHack - go read those docs. -If that's unclear, or you need more help checkout our [support page](https://docs.dfhack.org/en/latest/docs/Support.html) for up-to-date options. +If the docs are unclear or you need more help, please check out our [support page](https://docs.dfhack.org/en/latest/docs/Support.html) for ways to contact the DFHack developers. From 888c5317744189d18293aab71c93d551f248ef83 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 27 May 2022 00:33:44 -0400 Subject: [PATCH 069/854] Add mock.observe_func(), improve mock.lua documentation observe_func() is similar to func() but passes through all calls to a specified function. --- library/lua/test_util/mock.lua | 52 +++++++++++++++++++++++++++++++-- test/library/test_util/mock.lua | 37 ++++++++++++++++++++++- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/library/lua/test_util/mock.lua b/library/lua/test_util/mock.lua index c60646b77..8d253cc10 100644 --- a/library/lua/test_util/mock.lua +++ b/library/lua/test_util/mock.lua @@ -32,12 +32,17 @@ function _patch_impl(patches_raw, callback, restore_only) end --[[ + +Replaces `table[key]` with `value`, calls `callback()`, then restores the +original value of `table[key]`. + Usage: patch(table, key, value, callback) patch({ {table, key, value}, {table2, key2, value2}, }, callback) + ]] function mock.patch(...) local args = {...} @@ -57,12 +62,18 @@ function mock.patch(...) end --[[ + +Restores the original value of `table[key]` after calling `callback()`. + +Equivalent to: patch(table, key, table[key], callback) + Usage: restore(table, key, callback) restore({ {table, key}, {table2, key2}, }, callback) + ]] function mock.restore(...) local args = {...} @@ -81,9 +92,19 @@ function mock.restore(...) return _patch_impl(patches, callback, true) end -function mock.func(...) +--[[ + +Returns a callable object that tracks the arguments it is called with, then +passes those arguments to `callback()`. + +The returned object has the following properties: +- `call_count`: the number of times the object has been called +- `call_args`: a table of function arguments (shallow-copied) corresponding + to each time the object was called + +]] +function mock.observe_func(callback) local f = { - return_values = {...}, call_count = 0, call_args = {}, } @@ -101,11 +122,36 @@ function mock.func(...) end end table.insert(self.call_args, args) - return table.unpack(self.return_values) + return callback(...) end, }) return f end +--[[ + +Returns a callable object similar to `mock.observe_func()`, but which when +called, only returns the given `return_value`(s) with no additional side effects. + +Intended for use by `patch()`. + +Usage: + func(return_value [, return_value2 ...]) + +See `observe_func()` for a description of the return value. + +The return value also has an additional `return_values` field, which is a table +of values returned when the object is called. This can be modified. + +]] +function mock.func(...) + local f + f = mock.observe_func(function() + return table.unpack(f.return_values) + end) + f.return_values = {...} + return f +end + return mock diff --git a/test/library/test_util/mock.lua b/test/library/test_util/mock.lua index 32aed28e1..1031a496a 100644 --- a/test/library/test_util/mock.lua +++ b/test/library/test_util/mock.lua @@ -208,9 +208,44 @@ function test.func_call_return_value() end function test.func_call_return_multiple_values() - local f = mock.func(7,5,{imatable='snarfsnarf'}) + local f = mock.func(7, 5, {imatable='snarfsnarf'}) local a, b, c = f() expect.eq(7, a) expect.eq(5, b) expect.table_eq({imatable='snarfsnarf'}, c) end + +function test.observe_func() + -- basic end-to-end test for common cases; + -- most edge cases are covered by mock.func() tests + local counter = 0 + local function target() + counter = counter + 1 + return counter + end + local observer = mock.observe_func(target) + + expect.eq(observer(), 1) + expect.eq(counter, 1) + expect.eq(observer.call_count, 1) + expect.table_eq(observer.call_args, {{}}) + + expect.eq(observer('x', 'y'), 2) + expect.eq(counter, 2) + expect.eq(observer.call_count, 2) + expect.table_eq(observer.call_args, {{}, {'x', 'y'}}) +end + +function test.observe_func_error() + local function target() + error('asdf') + end + local observer = mock.observe_func(target) + + expect.error_match('asdf', function() + observer('x') + end) + -- make sure the call was still tracked + expect.eq(observer.call_count, 1) + expect.table_eq(observer.call_args, {{'x'}}) +end From 71d003c77dc735200b436082381d42d643a4f2e9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 27 May 2022 01:03:40 -0400 Subject: [PATCH 070/854] Ensure that test stack frames in the test file are printed Previously, only frames in the file that called `expect.*()` were printed. This change allows calling `expect.*()` from functions called by the files under test. See dfhack/scripts#385 for an example with `expect.fail()`. --- ci/test.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ci/test.lua b/ci/test.lua index 9a4a33ef2..aca78e851 100644 --- a/ci/test.lua +++ b/ci/test.lua @@ -240,7 +240,7 @@ end -- output doesn't trigger its own dfhack.printerr usage detection (see -- detect_printerr below) local orig_printerr = dfhack.printerr -local function wrap_expect(func, private) +local function wrap_expect(func, private, path) return function(...) private.checks = private.checks + 1 local ret = {func(...)} @@ -269,7 +269,7 @@ local function wrap_expect(func, private) end -- Skip any frames corresponding to C calls, or Lua functions defined in another file -- these could include pcall(), with_finalize(), etc. - if info.what == 'Lua' and info.short_src == caller_src then + if info.what == 'Lua' and (info.short_src == caller_src or info.short_src == path) then orig_printerr((' at %s:%d'):format(info.short_src, info.currentline)) end frame = frame + 1 @@ -278,7 +278,7 @@ local function wrap_expect(func, private) end end -local function build_test_env() +local function build_test_env(path) local env = { test = utils.OrderedTable(), -- config values can be overridden in the test file to define @@ -309,7 +309,7 @@ local function build_test_env() checks_ok = 0, } for name, func in pairs(expect) do - env.expect[name] = wrap_expect(func, private) + env.expect[name] = wrap_expect(func, private, path) end setmetatable(env, {__index = _G}) return env, private @@ -345,9 +345,9 @@ local function finish_tests(done_command) end local function load_tests(file, tests) - local short_filename = file:sub((file:find('test') or -4)+5, -1) + local short_filename = file:sub((file:find('test') or -4) + 5, -1) print('Loading file: ' .. short_filename) - local env, env_private = build_test_env() + local env, env_private = build_test_env(file) local code, err = loadfile(file, 't', env) if not code then dfhack.printerr('Failed to load file: ' .. tostring(err)) From add8aa238609cddc3a5b7d470e5115a3a191d562 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 27 May 2022 07:18:24 +0000 Subject: [PATCH 071/854] Auto-update submodules depends/libexpat: dfhack --- depends/libexpat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/libexpat b/depends/libexpat index 3e877cbb3..d6c6a81e1 160000 --- a/depends/libexpat +++ b/depends/libexpat @@ -1 +1 @@ -Subproject commit 3e877cbb3c9bc8f22946053e70490d2e5431f4d5 +Subproject commit d6c6a81e1a34e646705274110d8fe8c7ec91405a From 07f54deb0bf609eeba54a1ae43856626f4d8bf9d Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sat, 28 May 2022 00:25:17 +0200 Subject: [PATCH 072/854] fix wrong `Label.frame_body.x2` value (#2134) * fix wrong `Label.frame_body.x2` value `update_scroll_inset` might change `frame_inset`, i.e. we need to `computeFrame` with the new values. * add tests for Label * add missing `local`, remove code in comments * move `TestFramedScreen` outside test functions --- library/lua/gui/widgets.lua | 12 +++- test/library/gui/widgets.Label.lua | 94 ++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 test/library/gui/widgets.Label.lua diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 21e03370b..c6e84d7a1 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -478,8 +478,16 @@ function Label:render_scroll_icons(dc, x, y1, y2) end end -function Label:postComputeFrame() - self:update_scroll_inset() +function Label:computeFrame(parent_rect) + local frame_rect,body_rect = Label.super.computeFrame(self, parent_rect) + + self.frame_rect = frame_rect + self.frame_body = parent_rect:viewport(body_rect or frame_rect) + + self:update_scroll_inset() -- frame_body is now set + + -- recalc with updated frame_inset + return Label.super.computeFrame(self, parent_rect) end function Label:preUpdateLayout() diff --git a/test/library/gui/widgets.Label.lua b/test/library/gui/widgets.Label.lua new file mode 100644 index 000000000..49a75a235 --- /dev/null +++ b/test/library/gui/widgets.Label.lua @@ -0,0 +1,94 @@ +-- test -dhack/scripts/devel/tests -twidgets.Label + +local gui = require('gui') +local widgets = require('gui.widgets') + +local xtest = {} -- use to temporarily disable tests (change `function xtest.somename` to `function xxtest.somename`) +local wait = function(n) + delay(n or 30) -- enable for debugging the tests +end + +local fs = defclass(fs, gui.FramedScreen) +fs.ATTRS = { + frame_style = gui.GREY_LINE_FRAME, + frame_title = 'TestFramedScreen', + frame_width = 10, + frame_height = 10, + frame_inset = 0, + focus_path = 'test-framed-screen', +} + +function test.Label_correct_frame_body_with_scroll_icons() + local t = {} + for i = 1, 12 do + t[#t+1] = tostring(i) + t[#t+1] = NEWLINE + end + + function fs:init(args) + self:addviews{ + widgets.Label{ + view_id = 'text', + frame_inset = 0, + text = t, + --show_scroll_icons = 'right', + }, + } + end + + local o = fs{} + --o:show() + --wait() + expect.eq(o.subviews.text.frame_body.width, 9, "Label's frame_body.x2 and .width should be one smaller because of show_scroll_icons.") + --o:dismiss() +end + +function test.Label_correct_frame_body_with_few_text_lines() + local t = {} + for i = 1, 10 do + t[#t+1] = tostring(i) + t[#t+1] = NEWLINE + end + + function fs:init(args) + self:addviews{ + widgets.Label{ + view_id = 'text', + frame_inset = 0, + text = t, + --show_scroll_icons = 'right', + }, + } + end + + local o = fs{} + --o:show() + --wait() + expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.") + --o:dismiss() +end + +function test.Label_correct_frame_body_without_show_scroll_icons() + local t = {} + for i = 1, 12 do + t[#t+1] = tostring(i) + t[#t+1] = NEWLINE + end + + function fs:init(args) + self:addviews{ + widgets.Label{ + view_id = 'text', + frame_inset = 0, + text = t, + show_scroll_icons = false, + }, + } + end + + local o = fs{} + --o:show() + --wait() + expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.") + --o:dismiss() +end From ea7fe2b926f7478171290465df8f1eff2c5567c8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 27 May 2022 15:38:13 -0700 Subject: [PATCH 073/854] account for scroll bars when sizing message boxes --- library/lua/gui/dialogs.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 51f346bbd..952e8f734 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -36,7 +36,13 @@ end function MessageBox:getWantedFrameSize() local label = self.subviews.label local width = math.max(self.frame_width or 0, 20, #(self.frame_title or '') + 4) - return math.max(width, label:getTextWidth()), label:getTextHeight() + local text_area_width = label:getTextWidth() + if label.frame_inset then + -- account for scroll icons + text_area_width = text_area_width + (label.frame_inset.l or 0) + text_area_width = text_area_width + (label.frame_inset.r or 0) + end + return math.max(width, text_area_width), label:getTextHeight() end function MessageBox:onRenderFrame(dc,rect) From 9d2bb01cafd63e8b8ba4d5462814839c06f34231 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 28 May 2022 07:17:45 +0000 Subject: [PATCH 074/854] Auto-update submodules scripts: master depends/libexpat: dfhack --- depends/libexpat | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/libexpat b/depends/libexpat index d6c6a81e1..6ac8628a3 160000 --- a/depends/libexpat +++ b/depends/libexpat @@ -1 +1 @@ -Subproject commit d6c6a81e1a34e646705274110d8fe8c7ec91405a +Subproject commit 6ac8628a3c7a1677b27fb007db96f665b684a183 diff --git a/scripts b/scripts index b1bfe27aa..741c84ada 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b1bfe27aa95ceb6c645f40c412c34d92c74b4bb0 +Subproject commit 741c84ada2ec7fdd0083744afab294d9a1b6e370 From b1e118384e2c88aed70efd5ec7f0c5e1a1b2d8fc Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 30 May 2022 15:23:07 -0700 Subject: [PATCH 075/854] Update Maps.cpp --- library/modules/Maps.cpp | 254 +++++++++++++++++++-------------------- 1 file changed, 126 insertions(+), 128 deletions(-) diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 06f8c9673..e046faa0e 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -824,162 +824,160 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ -namespace { - //----------------------------------------------------------------------------// - // Utility function - // - //----------------------------------------------------------------------------// - std::pair check_tropicality(df::region_map_entry& region, - int y_pos - ) +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +static std::pair check_tropicality(df::region_map_entry& region, + int y_pos +) +{ + int flip_latitude = df::global::world->world_data->flip_latitude; + + bool is_possible_tropical_area_by_latitude = false; + bool is_tropical_area_by_latitude = false; + + if (flip_latitude == -1) // NO POLES { - int flip_latitude = df::global::world->world_data->flip_latitude; + // If there're no poles, tropical area is determined by temperature + is_possible_tropical_area_by_latitude = region.temperature >= 75; + is_tropical_area_by_latitude = region.temperature >= 85; + } - bool is_possible_tropical_area_by_latitude = false; - bool is_tropical_area_by_latitude = false; + else + { + int v6 = 0; + + df::world_data* wdata = df::global::world->world_data; - if (flip_latitude == -1) // NO POLES + if (flip_latitude == 0) // NORTH POLE ONLY { - // If there're no poles, tropical area is determined by temperature - is_possible_tropical_area_by_latitude = region.temperature >= 75; - is_tropical_area_by_latitude = region.temperature >= 85; + v6 = y_pos; } - else + else if (flip_latitude == 1) // SOUTH_POLE ONLY { - int v6 = 0; - - df::world_data* wdata = df::global::world->world_data; - - if (flip_latitude == 0) // NORTH POLE ONLY - { - v6 = y_pos; - } - - else if (flip_latitude == 1) // SOUTH_POLE ONLY - { - v6 = df::global::world->world_data->world_height - y_pos - 1; - } + v6 = df::global::world->world_data->world_height - y_pos - 1; + } - else if (flip_latitude == 2) // BOTH POLES + else if (flip_latitude == 2) // BOTH POLES + { + if (y_pos < wdata->world_height / 2) + v6 = 2 * y_pos; + else { - if (y_pos < wdata->world_height / 2) - v6 = 2 * y_pos; - else - { - v6 = wdata->world_height + 2 * (wdata->world_height / 2 - y_pos) - 1; - if (v6 < 0) - v6 = 0; - if (v6 >= wdata->world_height) - v6 = wdata->world_height - 1; - } + v6 = wdata->world_height + 2 * (wdata->world_height / 2 - y_pos) - 1; + if (v6 < 0) + v6 = 0; + if (v6 >= wdata->world_height) + v6 = wdata->world_height - 1; } - - if (wdata->world_height == 17) - v6 *= 16; - else if (wdata->world_height == 33) - v6 *= 8; - else if (wdata->world_height == 65) - v6 *= 4; - else if (wdata->world_height == 129) - v6 *= 2; - - is_possible_tropical_area_by_latitude = v6 > 170; - is_tropical_area_by_latitude = v6 >= 200; } - return std::pair(is_possible_tropical_area_by_latitude, - is_tropical_area_by_latitude - ); + if (wdata->world_height == 17) + v6 *= 16; + else if (wdata->world_height == 33) + v6 *= 8; + else if (wdata->world_height == 65) + v6 *= 4; + else if (wdata->world_height == 129) + v6 *= 2; + + is_possible_tropical_area_by_latitude = v6 > 170; + is_tropical_area_by_latitude = v6 >= 200; } + return std::pair(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} + - //----------------------------------------------------------------------------// - // Utility function - // - // return some unknow parameter as a percentage - //----------------------------------------------------------------------------// - int get_region_parameter(int y, - int x - ) +//----------------------------------------------------------------------------// +// Utility function +// +// return some unknow parameter as a percentage +//----------------------------------------------------------------------------// +static int get_region_parameter(int y, + int x +) +{ + int world_height = df::global::world->world_data->world_height; + if (world_height > 65) // Medium and large worlds { - int world_height = df::global::world->world_data->world_height; - if (world_height > 65) // Medium and large worlds - { - // access to region 2D array - df::region_map_entry& region = df::global::world->world_data->region_map[x][y]; - int flip_latitude = df::global::world->world_data->flip_latitude; - int rainfall = region.rainfall; - int result; - int y_pos = y; - int ypos = y_pos; - - if (flip_latitude == -1) // NO POLES - return 100; - - else if (flip_latitude == 1) // SOUTH POLE - ypos = world_height - y_pos - 1; - else if (flip_latitude == 2) // NORTH & SOUTH POLE - { - if (ypos < world_height / 2) - ypos *= 2; - else - { - ypos = world_height + 2 * (world_height / 2 - ypos) - 1; - if (ypos < 0) - ypos = 0; - if (ypos >= world_height) - ypos = world_height - 1; - } - } + // access to region 2D array + df::region_map_entry& region = df::global::world->world_data->region_map[x][y]; + int flip_latitude = df::global::world->world_data->flip_latitude; + int rainfall = region.rainfall; + int result; + int y_pos = y; + int ypos = y_pos; - int latitude; // 0 - 256 (size of a large world) - switch (world_height) + if (flip_latitude == -1) // NO POLES + return 100; + + else if (flip_latitude == 1) // SOUTH POLE + ypos = world_height - y_pos - 1; + else if (flip_latitude == 2) // NORTH & SOUTH POLE + { + if (ypos < world_height / 2) + ypos *= 2; + else { - case 17: // Pocket world - latitude = 16 * ypos; - break; - case 33: // Smaller world - latitude = 8 * ypos; - break; - case 65: // Small world - latitude = 4 * ypos; - break; - case 129: // Medium world - latitude = 2 * ypos; - break; - default: // Large world - latitude = ypos; - break; + ypos = world_height + 2 * (world_height / 2 - ypos) - 1; + if (ypos < 0) + ypos = 0; + if (ypos >= world_height) + ypos = world_height - 1; } + } - // latitude > 220 - if ((latitude - 171) > 49) - return 100; + int latitude; // 0 - 256 (size of a large world) + switch (world_height) + { + case 17: // Pocket world + latitude = 16 * ypos; + break; + case 33: // Smaller world + latitude = 8 * ypos; + break; + case 65: // Small world + latitude = 4 * ypos; + break; + case 129: // Medium world + latitude = 2 * ypos; + break; + default: // Large world + latitude = ypos; + break; + } + // latitude > 220 + if ((latitude - 171) > 49) + return 100; - // Latitude between 191 and 200 - if ((latitude > 190) && (latitude < 201)) - return 0; - // Latitude between 201 and 220 - if ((latitude > 190) && (latitude >= 201)) - result = rainfall + 16 * (latitude - 207); - else - // Latitude between 0 and 190 - result = (16 * (184 - latitude) - rainfall); + // Latitude between 191 and 200 + if ((latitude > 190) && (latitude < 201)) + return 0; - if (result < 0) - return 0; + // Latitude between 201 and 220 + if ((latitude > 190) && (latitude >= 201)) + result = rainfall + 16 * (latitude - 207); + else + // Latitude between 0 and 190 + result = (16 * (184 - latitude) - rainfall); - if (result > 100) - return 100; + if (result < 0) + return 0; - return result; - } + if (result > 100) + return 100; - return 100; + return result; } + + return 100; } From bc0def4342e3a097cf5f8e6e4a362f91dd4dba3a Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 30 May 2022 19:58:46 -0700 Subject: [PATCH 076/854] MiscUtils word_wrap: Add option to trim only leading whitespace after wrapping (#2169) * Update changelog.txt --- docs/changelog.txt | 1 + library/MiscUtils.cpp | 16 ++++++++++------ library/include/MiscUtils.h | 8 +++++++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index b2ef0e90a..3b75390dd 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -47,6 +47,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Documentation ## API +- ``word_wrap``: argument ``bool collapse_whitespace`` converted to enum ``word_wrap_whitespace_mode mode``, with valid modes ``WSMODE_KEEP_ALL``, ``WSMODE_COLLAPSE_ALL``, and ``WSMODE_TRIM_LEADING``. ## Lua - ``widgets.HotkeyLabel``: the ``key_sep`` string is now configurable diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 6ab63d5eb..b959e756e 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -168,15 +168,15 @@ std::string to_search_normalized(const std::string &str) return result; } - -bool word_wrap(std::vector *out, const std::string &str, - size_t line_length, bool collapse_whitespace) +bool word_wrap(std::vector *out, const std::string &str, size_t line_length, + word_wrap_whitespace_mode mode) { if (line_length == 0) line_length = SIZE_MAX; std::string line; size_t break_pos = 0; + bool ignore_whitespace = false; for (auto &c : str) { @@ -185,19 +185,22 @@ bool word_wrap(std::vector *out, const std::string &str, out->push_back(line); line.clear(); break_pos = 0; + ignore_whitespace = (mode == WSMODE_TRIM_LEADING); continue; } if (isspace(c)) { - if (break_pos == line.length() && collapse_whitespace) + if (ignore_whitespace || (mode == WSMODE_COLLAPSE_ALL && break_pos == line.length())) continue; - line.push_back(collapse_whitespace ? ' ' : c); + line.push_back((mode == WSMODE_COLLAPSE_ALL) ? ' ' : c); break_pos = line.length(); } - else { + else + { line.push_back(c); + ignore_whitespace = false; } if (line.length() > line_length) @@ -215,6 +218,7 @@ bool word_wrap(std::vector *out, const std::string &str, } line = line.substr(break_pos); break_pos = 0; + ignore_whitespace = (mode == WSMODE_TRIM_LEADING); } } if (line.length()) diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index 764b11413..56506f7bd 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -389,10 +389,16 @@ DFHACK_EXPORT std::string toUpper(const std::string &str); DFHACK_EXPORT std::string toLower(const std::string &str); DFHACK_EXPORT std::string to_search_normalized(const std::string &str); +enum word_wrap_whitespace_mode { + WSMODE_KEEP_ALL, + WSMODE_COLLAPSE_ALL, + WSMODE_TRIM_LEADING +}; + DFHACK_EXPORT bool word_wrap(std::vector *out, const std::string &str, size_t line_length = 80, - bool collapse_whitespace = false); + word_wrap_whitespace_mode mode = WSMODE_KEEP_ALL); inline bool bits_match(unsigned required, unsigned ok, unsigned mask) { From ad2d9cad037a94c677e94df6fff9d303677d4f86 Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 1 Jun 2022 17:42:13 -0700 Subject: [PATCH 077/854] [lua] implement keyboard focus subsystem (#2160) * implement keyboard focus subsystem * Fix error in focus group combining * documentation for the inputToSubviews decision * modify unit tests to catch that last bug --- docs/Lua API.rst | 39 ++++++++-- docs/changelog.txt | 1 + library/lua/gui.lua | 98 +++++++++++++++++++++++- test/library/gui.lua | 176 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 308 insertions(+), 6 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 04257b49d..2edb1b015 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3516,14 +3516,24 @@ The class defines the following attributes: :visible: Specifies that the view should be painted. :active: Specifies that the view should receive events, if also visible. :view_id: Specifies an identifier to easily identify the view among subviews. - This is reserved for implementation of top-level views, and should - not be used by widgets for their internal subviews. + This is reserved for use by script writers and should not be set by + library widgets for their internal subviews. +:on_focus: Called when the view gains keyboard focus; see ``setFocus()`` below. +:on_unfocus: Called when the view loses keyboard focus. It also always has the following fields: :subviews: Contains a table of all subviews. The sequence part of the table is used for iteration. In addition, subviews are also - indexed under their *view_id*, if any; see ``addviews()`` below. + indexed under their ``view_id``, if any; see ``addviews()`` below. +:parent_view: A reference to the parent view. This field is ``nil`` until the + view is added as a subview to another view with ``addviews()``. +:focus_group: The list of widgets in a hierarchy. This table is unique and empty + when a view is initialized, but is replaced by a shared table when + the view is added to a parent via ``addviews()``. If a view in the + focus group has keyboard focus, that widget can be accessed via + ``focus_group.cur``. +:focus: A boolean indicating whether the view currently has keyboard focus. These fields are computed by the layout process: @@ -3617,8 +3627,27 @@ The class has the following methods: Calls ``onInput`` on all visible active subviews, iterating the ``subviews`` sequence in *reverse order*, so that topmost subviews get events first. - Returns *true* if any of the subviews handled the event. - + Returns ``true`` if any of the subviews handled the event. If a subview within + the view's ``focus_group`` has focus and it and all of its ancestors are + active and visible, that subview is offered the chance to handle the input + before any other subviews. + +* ``view:getPreferredFocusState()`` + + Returns ``false`` by default, but should be overridden by subclasses that may + want to take keyboard focus (if it is unclaimed) when they are added to a + parent view with ``addviews()``. + +* ``view:setFocus(focus)`` + + Sets the keyboard focus to the view if ``focus`` is ``true``, or relinquishes + keyboard focus if ``focus`` is ``false``. Views that newly acquire keyboard + focus will trigger the ``on_focus`` callback, and views that lose keyboard + focus will trigger the ``on_unfocus`` callback. While a view has focus, all + keyboard input is sent to that view before any of its siblings or parents. + Keyboard input is propagated as normal (see ``inputToSubviews()`` above) if + there is no view with focus or if the view with focus returns ``false`` from + its ``onInput()`` function. .. _lua-gui-screen: diff --git a/docs/changelog.txt b/docs/changelog.txt index 3b75390dd..0a4bcd68a 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -50,6 +50,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``word_wrap``: argument ``bool collapse_whitespace`` converted to enum ``word_wrap_whitespace_mode mode``, with valid modes ``WSMODE_KEEP_ALL``, ``WSMODE_COLLAPSE_ALL``, and ``WSMODE_TRIM_LEADING``. ## Lua +- ``gui.View``: all ``View`` subclasses (including all ``Widgets``) can now acquire keyboard focus with the new ``View:setFocus()`` function. See docs for details. - ``widgets.HotkeyLabel``: the ``key_sep`` string is now configurable - ``widgets.EditField``: the ``key_sep`` string is now configurable - ``widgets.EditField``: can now display an optional string label in addition to the activation key diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 8521e1dfe..8d546b21a 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -376,10 +376,21 @@ View.ATTRS { active = true, visible = true, view_id = DEFAULT_NIL, + on_focus = DEFAULT_NIL, + on_unfocus = DEFAULT_NIL, } function View:init(args) self.subviews = {} + self.focus_group = {self} + self.focus = false +end + +local function inherit_focus_group(view, focus_group) + for _,child in ipairs(view.subviews) do + inherit_focus_group(child, focus_group) + end + view.focus_group = focus_group end function View:addviews(list) @@ -388,6 +399,31 @@ function View:addviews(list) local sv = self.subviews for _,obj in ipairs(list) do + -- absorb the focus groups of new children + for _,focus_obj in ipairs(obj.focus_group) do + table.insert(self.focus_group, focus_obj) + end + -- if the child's focus group has a focus owner, absorb it if we don't + -- already have one. otherwise keep the focus owner we have and clear + -- the focus of the child. + if obj.focus_group.cur then + if not self.focus_group.cur then + self.focus_group.cur = obj.focus_group.cur + else + obj.focus_group.cur:setFocus(false) + end + end + -- overwrite the child's focus_group hierarchy with ours + inherit_focus_group(obj, self.focus_group) + -- if we don't have a focus owner, give it to the new child if they want + if not self.focus_group.cur and obj:getPreferredFocusState() then + obj:setFocus(true) + end + + -- set ourselves as the parent view of the new child + obj.parent_view = self + + -- add the subview to our child list table.insert(sv, obj) local id = obj.view_id @@ -405,6 +441,33 @@ function View:addviews(list) end end +-- should be overridden by widgets that care about capturing keyboard focus +-- (e.g. widgets.EditField) +function View:getPreferredFocusState() + return false +end + +function View:setFocus(focus) + if focus then + if self.focus then return end -- nothing to do if we already have focus + if self.focus_group.cur then + -- steal focus from current owner + self.focus_group.cur:setFocus(false) + end + self.focus_group.cur = self + self.focus = true + if self.on_focus then + self.on_focus() + end + elseif self.focus then + self.focus = false + self.focus_group.cur = nil + if self.on_unfocus then + self.on_unfocus() + end + end +end + function View:getWindowSize() local rect = self.frame_body return rect.width, rect.height @@ -476,12 +539,45 @@ end function View:onRenderBody(dc) end +-- Returns whether we should invoke the focus owner's onInput() function from +-- the given view's inputToSubviews() function. That is, returns true if: +-- - the view is not itself the focus owner since that would be an infinite loop +-- - the view is not a descendent of the focus owner (same as above) +-- - the focus owner and all of its ancestors are visible and active, since if +-- the focus owner is not (directly or transitively) visible or active, then +-- it shouldn't be getting input. +local function should_send_input_to_focus_owner(view, focus_owner) + local iter = view + while iter do + if iter == focus_owner then + return false + end + iter = iter.parent_view + end + iter = focus_owner + while iter do + if not iter.visible or not iter.active then + return false + end + iter = iter.parent_view + end + return true +end + function View:inputToSubviews(keys) local children = self.subviews + -- give focus owner first dibs on the input + local focus_owner = self.focus_group.cur + if focus_owner and should_send_input_to_focus_owner(self, focus_owner) and + focus_owner:onInput(keys) then + return true + end + for i=#children,1,-1 do local child = children[i] - if child.visible and child.active and child:onInput(keys) then + if child.visible and child.active and child ~= focus_owner and + child:onInput(keys) then return true end end diff --git a/test/library/gui.lua b/test/library/gui.lua index 4cfad07b1..fe04614d6 100644 --- a/test/library/gui.lua +++ b/test/library/gui.lua @@ -17,3 +17,179 @@ function test.clear_pen() tile_color = false, }) end + +WantsFocusView = defclass(WantsFocusView, gui.View) +function WantsFocusView:getPreferredFocusState() + return true +end + +function test.view_wants_focus() + local parent = gui.View() + expect.false_(parent.focus) + + -- expect first (regular) child to not get focus + local regular_child = gui.View() + expect.false_(regular_child.focus) + expect.ne(parent.focus_group, regular_child.focus_group) + parent:addviews{regular_child} + expect.false_(regular_child.focus) + expect.eq(parent.focus_group, regular_child.focus_group) + + -- the first child who wants focus gets it + local focus_child = WantsFocusView() + expect.false_(focus_child.focus) + parent:addviews{focus_child} + expect.true_(focus_child.focus) + expect.eq(parent.focus_group.cur, focus_child) + + -- the second child who wants focus doesn't + local focus_child2 = WantsFocusView() + parent:addviews{focus_child2} + expect.false_(focus_child2.focus) + expect.eq(parent.focus_group.cur, focus_child) +end + +function test.inherit_focus_from_subview() + local parent = gui.View() + local regular_child = gui.View() + local focus_child = WantsFocusView() + regular_child:addviews{focus_child} + expect.true_(focus_child.focus) + parent:addviews{regular_child} + expect.eq(parent.focus_group.cur, focus_child) +end + +function test.subviews_negotiate_focus() + local parent = gui.View() + local regular_child = gui.View() + local regular_child2 = gui.View() + local focus_child = WantsFocusView() + local focus_child2 = WantsFocusView() + regular_child:addviews{focus_child} + regular_child2:addviews{focus_child2} + expect.true_(focus_child.focus) + expect.true_(focus_child2.focus) + expect.ne(regular_child.focus_group, regular_child2.focus_group) + parent:addviews{regular_child} + expect.eq(parent.focus_group.cur, focus_child) + expect.true_(focus_child.focus) + expect.true_(focus_child2.focus) + parent:addviews{regular_child2} + expect.eq(parent.focus_group.cur, focus_child) + expect.eq(regular_child.focus_group, regular_child2.focus_group) + expect.true_(focus_child.focus) + expect.false_(focus_child2.focus) +end + +MockInputView = defclass(MockInputView, gui.View) +function MockInputView:onInput(keys) + self.mock(keys) + MockInputView.super.onInput(self, keys) + return true +end + +local function reset_child_mocks(parent) + for _,child in ipairs(parent.subviews) do + child.mock = mock.func() + reset_child_mocks(child) + end +end + +-- verify that input got routed as expected +local function test_children(expected, parent) + local children = parent.subviews + for i,val in ipairs(expected) do + expect.eq(val, children[i].mock.call_count, 'child '..i) + end +end + +function test.keyboard_follows_focus() + local parent = gui.View() + local regular_child = MockInputView{} + local regular_child2 = MockInputView{} + local last_child = MockInputView{} + parent:addviews{regular_child, regular_child2, last_child} + + reset_child_mocks(parent) + parent:onInput({'a'}) + test_children({0,0,1}, parent) + + regular_child:setFocus(true) + reset_child_mocks(parent) + parent:onInput({'a'}) + test_children({1,0,0}, parent) + + regular_child2:setFocus(true) + reset_child_mocks(parent) + parent:onInput({'a'}) + test_children({0,1,0}, parent) + + regular_child2:setFocus(false) + reset_child_mocks(parent) + parent:onInput({'a'}) + test_children({0,0,1}, parent) +end + +function test.one_callback_on_double_focus() + local on_focus = mock.func() + local view = gui.View{on_focus=on_focus} + expect.eq(0, on_focus.call_count) + view:setFocus(true) + expect.eq(1, on_focus.call_count) + view:setFocus(true) + expect.eq(1, on_focus.call_count) +end + +function test.one_callback_on_double_unfocus() + local on_unfocus = mock.func() + local view = gui.View{on_unfocus=on_unfocus} + expect.eq(0, on_unfocus.call_count) + view:setFocus(false) + expect.eq(0, on_unfocus.call_count) + view:setFocus(true) + expect.eq(0, on_unfocus.call_count) + view:setFocus(false) + expect.eq(1, on_unfocus.call_count) + view:setFocus(false) + expect.eq(1, on_unfocus.call_count) +end + +function test.no_input_when_focus_owner_is_hidden() + local parent = gui.View() + local child1 = MockInputView() + local child2 = MockInputView() + parent:addviews{child1, child2} + child1:setFocus(true) + child1.visible = false + reset_child_mocks(parent) + parent:onInput({'a'}) + test_children({0,1}, parent) +end + +function test.no_input_when_ancestor_is_hidden() + local grandparent = gui.View() + local parent = MockInputView() + local child1 = MockInputView() + local child2 = MockInputView() + grandparent:addviews{parent} + parent:addviews{child1, child2} + child1:setFocus(true) + parent.visible = false + reset_child_mocks(grandparent) + grandparent:onInput({'a'}) + test_children({0}, grandparent) + test_children({0,0}, parent) +end + +function test.no_input_loop_in_children_of_focus_owner() + local grandparent = gui.View() + local parent = MockInputView() + local child = MockInputView() + grandparent:addviews{parent} + parent:addviews{child} + parent:setFocus(true) + reset_child_mocks(grandparent) + child:onInput({'a'}) + test_children({0}, grandparent) + test_children({1}, parent) +end From 89d3d45e875d173a588e1ce2795a48af087bd3e0 Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 1 Jun 2022 21:48:21 -0700 Subject: [PATCH 078/854] Allow EditField widgets to manage their own activation and keyboard focus (#2147) * use new focus subsystem in widgets.EditField * always eat the enter key if we have an on_submit * add modal attribute * give EditFields a default height of 1 so they can be autoarranged --- docs/Lua API.rst | 17 ++++++++++ docs/changelog.txt | 1 + library/lua/gui/widgets.lua | 63 ++++++++++++++++++++++++++----------- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 2edb1b015..bbcf0b145 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3871,6 +3871,23 @@ Attributes: :key: If specified, the field is disabled until this key is pressed. Must be given as a string. :key_sep: If specified, will be used to customize how the activation key is displayed. See ``token.key_sep`` in the ``Label`` documentation below. +:modal: Whether the ``EditField`` should prevent input from propagating to other + widgets while it has focus. You can set this to ``true``, for example, + if you don't want a ``List`` widget to react to arrow keys while the + user is editing. + +An ``EditField`` will only read and process text input if it has keyboard focus. +It will automatically acquire keyboard focus when it is added as a subview to +a parent that has not already granted keyboard focus to another widget. If you +have more than one ``EditField`` on a screen, you can select which has focus by +calling ``setFocus(true)`` on the field object. + +If an activation ``key`` is specified, the ``EditField`` will manage its own +focus. It will start in the unfocused state, and pressing the activation key +will acquire keyboard focus. Pressing the Enter key will release keyboard focus +and then call the ``on_submit`` callback. Pressing the Escape key will also +release keyboard focus, but first it will restore the text that was displayed +before the ``EditField`` gained focus and then call the ``on_change`` callback. Label class ----------- diff --git a/docs/changelog.txt b/docs/changelog.txt index 0a4bcd68a..a777b60c4 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -54,6 +54,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``widgets.HotkeyLabel``: the ``key_sep`` string is now configurable - ``widgets.EditField``: the ``key_sep`` string is now configurable - ``widgets.EditField``: can now display an optional string label in addition to the activation key +- ``widgets.EditField``: views that have an ``EditField`` subview no longer need to manually manage the ``EditField`` activation state and input routing. This is now handled automatically by the new ``gui.View`` keyboard focus subsystem. # 0.47.05-r5 diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index c6e84d7a1..1a1b4c6fb 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -186,13 +186,25 @@ EditField.ATTRS{ on_submit = DEFAULT_NIL, key = DEFAULT_NIL, key_sep = DEFAULT_NIL, + frame = {h=1}, + modal = false, } function EditField:init() + local function on_activate() + self.saved_text = self.text + self:setFocus(true) + end + self:addviews{HotkeyLabel{frame={t=0,l=0}, key=self.key, key_sep=self.key_sep, - label=self.label_text}} + label=self.label_text, + on_activate=self.key and on_activate or nil}} +end + +function EditField:getPreferredFocusState() + return not self.key end function EditField:postUpdateLayout() @@ -203,7 +215,7 @@ function EditField:onRenderBody(dc) dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0) local cursor = '_' - if not self.active or gui.blink_visible(300) then + if not self.active or not self.focus or gui.blink_visible(300) then cursor = ' ' end local txt = self.text .. cursor @@ -215,12 +227,36 @@ function EditField:onRenderBody(dc) end function EditField:onInput(keys) - if self.on_submit and keys.SELECT then - self.on_submit(self.text) + if not self.focus then + -- only react to our hotkey + return self:inputToSubviews(keys) + end + + if self.key and keys.LEAVESCREEN then + local old = self.text + self.text = self.saved_text + if self.on_change and old ~= self.saved_text then + self.on_change(self.text, old) + end + self:setFocus(false) return true - elseif keys._STRING then + end + + if keys.SELECT then + if self.key then + self:setFocus(false) + end + if self.on_submit then + self.on_submit(self.text) + return true + end + return not not self.key + end + + if keys._STRING then local old = self.text if keys._STRING == 0 then + -- handle backspace self.text = string.sub(old, 1, #old-1) else local cv = string.char(keys._STRING) @@ -233,6 +269,9 @@ function EditField:onInput(keys) end return true end + + -- if we're modal, then unconditionally eat all the input + return self.modal end ----------- @@ -957,7 +996,6 @@ function FilteredList:init(info) on_change = self:callback('onFilterChange'), on_char = self:callback('onFilterChar'), key = self.edit_key, - active = (self.edit_key == nil), } self.list = List{ frame = { t = 2 }, @@ -1002,19 +1040,6 @@ function FilteredList:init(info) end end -function FilteredList:onInput(keys) - if self.edit_key and keys[self.edit_key] and not self.edit.active then - self.edit.active = true - return true - elseif keys.LEAVESCREEN and self.edit.active then - self.edit.active = false - return true - else - return self:inputToSubviews(keys) - end -end - - function FilteredList:getChoices() return self.choices end From f5d3b9f6995f46d11668a9098fb722d3352ba081 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 2 Jun 2022 04:51:00 +0000 Subject: [PATCH 079/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 1dfe6c5ab..f503096c1 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 1dfe6c5ab9887507cdcdebdd9390352fe0bba2dd +Subproject commit f503096c130661dda7a9b7e4b471b0854981f0f5 diff --git a/scripts b/scripts index 741c84ada..1b1a28fa6 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 741c84ada2ec7fdd0083744afab294d9a1b6e370 +Subproject commit 1b1a28fa64bdaf17da8e6257f48deafd74dfd149 From 27bf4c758beaa1d4b47832a2e090fdf3db5393ac Mon Sep 17 00:00:00 2001 From: Myk Date: Thu, 2 Jun 2022 06:24:13 -0700 Subject: [PATCH 080/854] Allow player to pause the confirmation dialog without disabling (#2164) * basic pause functionality for confirm * update changelog * wrap the pause message and output in white * unpause on viewscreen transition when we can but still use esc detection when we won't get a viewscreen transition (like when we're intercepting input on viewscreen_dwarfmodest * add more code docs about unpause detection --- docs/changelog.txt | 1 + plugins/confirm.cpp | 78 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a777b60c4..4dd8404ac 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - `confirm`: added a confirmation dialog for removing manager orders +- `confirm`: allow players to pause the confirmation dialog until they exit the current screen - `dfhack-examples-guide`: refine food preparation orders and fix conditions for making jugs and pots in the ``basic`` manager orders ## Documentation diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index 6a2798cb4..60735ef94 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -17,6 +17,7 @@ #include "df/building_tradedepotst.h" #include "df/general_ref.h" #include "df/general_ref_contained_in_itemst.h" +#include "df/interfacest.h" #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_jobmanagementst.h" #include "df/viewscreen_justicest.h" @@ -44,6 +45,12 @@ static map confirmations; string active_id; queue cmds; +// true when confirm is paused +bool paused = false; +// if set, confirm will unpause when this screen is no longer on the stack +df::viewscreen *paused_screen = NULL; + + template inline bool in_vector (std::vector &vec, FT item) { @@ -229,6 +236,18 @@ namespace conf_lua { lua_pushnil(L); return 1; } + int unpause(lua_State *) + { + Core::getInstance().print("unpausing\n"); + paused = false; + paused_screen = NULL; + return 0; + } + int get_paused (lua_State *L) + { + Lua::Push(L, paused); + return 1; + } } } @@ -247,6 +266,8 @@ DFHACK_PLUGIN_LUA_COMMANDS { CONF_LUA_CMD(get_ids), CONF_LUA_CMD(get_conf_data), CONF_LUA_CMD(get_active_id), + CONF_LUA_CMD(unpause), + CONF_LUA_CMD(get_paused), DFHACK_LUA_END }; @@ -281,7 +302,15 @@ public: return true; } bool feed (ikey_set *input) { - if (state == INACTIVE) + if (paused) + { + // we can only detect that we've left the screen by intercepting the + // ESC key + if (!paused_screen && input->count(df::interface_key::LEAVESCREEN)) + conf_lua::api::unpause(NULL); + return false; + } + else if (state == INACTIVE) { for (df::interface_key key : *input) { @@ -302,6 +331,18 @@ public: set_state(INACTIVE); else if (input->count(df::interface_key::SELECT)) set_state(SELECTED); + else if (input->count(df::interface_key::CUSTOM_P)) + { + Core::getInstance().print("pausing\n"); + paused = true; + // only record the screen when we're not at the top viewscreen + // since this screen will *always* be on the stack. for + // dwarfmode screens, use ESC detection to discover when to + // unpause + if (!df::viewscreen_dwarfmodest::_identity.is_instance(screen)) + paused_screen = screen; + set_state(INACTIVE); + } else if (input->count(df::interface_key::CUSTOM_S)) show_options(); return true; @@ -316,6 +357,8 @@ public: } void render() { static vector lines; + static const std::string pause_message = + "Pause confirmations until you exit this screen"; Screen::Pen corner_ul = Screen::Pen((char)201, COLOR_GREY, COLOR_BLACK); Screen::Pen corner_ur = Screen::Pen((char)187, COLOR_GREY, COLOR_BLACK); Screen::Pen corner_dl = Screen::Pen((char)200, COLOR_GREY, COLOR_BLACK); @@ -329,7 +372,9 @@ public: for (string line : lines) max_length = std::max(max_length, line.size()); int width = max_length + 4; - int height = lines.size() + 4; + vector pause_message_lines; + word_wrap(&pause_message_lines, pause_message, max_length - 3); + int height = lines.size() + pause_message_lines.size() + 5; int x1 = (gps->dimx / 2) - (width / 2); int x2 = x1 + width - 1; int y1 = (gps->dimy / 2) - (height / 2); @@ -368,6 +413,14 @@ public: { Screen::paintString(Screen::Pen(' ', get_color(), COLOR_BLACK), x1 + 2, y1 + 2 + i, lines[i]); } + y = y1 + 3 + lines.size(); + for (size_t i = 0; i < pause_message_lines.size(); i++) + { + Screen::paintString(Screen::Pen(' ', COLOR_WHITE, COLOR_BLACK), x1 + 5, y + i, pause_message_lines[i]); + } + x = x1 + 2; + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::CUSTOM_P)); + OutputString(COLOR_WHITE, x, y, ":"); } else if (state == SELECTED) { @@ -533,6 +586,22 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) return CR_OK; } +static bool screen_found(df::viewscreen *target_screen) +{ + if (!df::global::gview) + return false; + + df::viewscreen *screen = &df::global::gview->view; + while (screen) + { + if (screen == target_screen) + return true; + screen = screen->child; + } + + return false; +} + DFhackCExport command_result plugin_onupdate (color_ostream &out) { while (!cmds.empty()) @@ -540,6 +609,11 @@ DFhackCExport command_result plugin_onupdate (color_ostream &out) Core::getInstance().runCommand(out, cmds.front()); cmds.pop(); } + + // if the screen that we paused on is no longer on the stack, unpause + if (paused_screen && !screen_found(paused_screen)) + conf_lua::api::unpause(NULL); + return CR_OK; } From 44d3a41f7978768d80eff5823a7a7005dc19ebdc Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 2 Jun 2022 20:19:21 +0000 Subject: [PATCH 081/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 1b1a28fa6..749208409 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 1b1a28fa64bdaf17da8e6257f48deafd74dfd149 +Subproject commit 749208409d909973de87e7f3edd7eccc824e52ed From 206944a00dc6378abbd1fc37bacb9c649e65d66e Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 2 Jun 2022 22:41:46 +0000 Subject: [PATCH 082/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 749208409..b70dec06c 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 749208409d909973de87e7f3edd7eccc824e52ed +Subproject commit b70dec06ca2a8622bce6cdece7153852356591f2 From ea44be9b9470e36f8e637c19b026e1c431637edc Mon Sep 17 00:00:00 2001 From: Myk Date: Thu, 2 Jun 2022 15:50:17 -0700 Subject: [PATCH 083/854] Enable fortress tests in CI (#2153) * download and cache the test save along with df Depends on us actually putting the test save on files.dfhack.org * run fortress tests in run-tests.py * use google drive link for now * make the save directory before extracting to it --- ci/download-df.sh | 33 +++++++++++++++++++++------------ ci/run-tests.py | 2 +- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/ci/download-df.sh b/ci/download-df.sh index 49dcedea7..2fd095c1d 100755 --- a/ci/download-df.sh +++ b/ci/download-df.sh @@ -2,7 +2,8 @@ set -e -tardest="df.tar.bz2" +df_tardest="df.tar.bz2" +save_tardest="test_save.tgz" selfmd5=$(openssl md5 < "$0") echo $selfmd5 @@ -16,40 +17,48 @@ cd "$DF_FOLDER/.." if [ -f receipt ]; then if [ "$selfmd5" != "$(cat receipt)" ]; then - echo "download-df.sh changed; removing DF" + echo "download-df.sh changed; re-downloading tarballs" rm receipt else - echo "Already downloaded $DF_VERSION" + echo "Already downloaded $DF_VERSION tarballs" fi fi if [ ! -f receipt ]; then - rm -f "$tardest" + rm -f "$df_tardest" "$save_tardest" minor=$(echo "$DF_VERSION" | cut -d. -f2) patch=$(echo "$DF_VERSION" | cut -d. -f3) - url="http://www.bay12games.com/dwarves/df_${minor}_${patch}_linux.tar.bz2" - echo Downloading + echo "Downloading DF $DF_VERSION" while read url; do echo "Attempting download: ${url}" - if wget -v "$url" -O "$tardest"; then + if wget -v "$url" -O "$df_tardest"; then break fi done < receipt diff --git a/ci/run-tests.py b/ci/run-tests.py index 11534b3f9..be2a02b3f 100755 --- a/ci/run-tests.py +++ b/ci/run-tests.py @@ -73,7 +73,7 @@ with open(test_init_file, 'w') as f: f.write(''' devel/dump-rpc dfhack-rpc.txt :lua dfhack.internal.addScriptPath(dfhack.getHackPath()) - test --resume --modes=none,title "lua scr.breakdown_level=df.interface_breakdown_types.%s" + test --resume -- lua scr.breakdown_level=df.interface_breakdown_types.%s ''' % ('NONE' if args.no_quit else 'QUIT')) test_config_file = 'test_config.json' From ed2ba697138d5807d370b6a94a45a4834cf4602f Mon Sep 17 00:00:00 2001 From: Myk Date: Thu, 2 Jun 2022 15:51:45 -0700 Subject: [PATCH 084/854] Improvements to dreamfort, quickfort keystroke aliases, and example orders (#2162) * add alt-f as a hotkey for quickfort to mimic the existing windows hotkey for the old quickfort hopefully this will ease the transition from the old quickfort to the new * add and document new quickfort aliases * reduce quantity requirement for dyeing 15 is just too high. the counter counts bags, not units * dreamfort blueprint improvements most of these suggested by ldog on the forums. Thanks! - significantly extend the list of hostile creatures that get stashed in the prisoner quantum stockpile - send adamantine thread to the metalworker stockpiles - give from thread/cloth stockpiles to clothier, loom, and dyer to protect the adamantine thread - automatically create tavern, library, and temple locations (restricted to residents only by default) - automatically associate the rented rooms with the tavern - place a stockpile under the dump zone so you can set up stockpile links for dumped items - doc improvements. in particular, point people to the new assign-minecarts tool for assigning minecarts to quantum stockpile dumps * update changelog * fix typo in cloth stockpile settings * fix typo in guildhall location setup * don't restrict stockpiles for clothiers and dyers * deprecate jugs alias and add stone|woodentools * remove dye thread, make jugs wooden remove dye thread to protect adamantine make jugs wooden to differentiate them from scroll rollers. that gives us a chance to actually have a usable jugs-only stockpile * dreamfort improvements - move trap corridor gates and levers before the walls and traps so they get constructed first - give useful names to the craftsdwarf's workshops - redesign the services level to: - fit better in a 1x1 embark - add doors to the hospital recovery rooms to protect from werebeasts-to-be - add an interrogation room (sheriff's office) next to the jail * shape hospital zone to exactly the hospital area * don't clutter the hospital with statues * update changelog --- data/blueprints/library/dreamfort.csv | 1157 ++++++++++++++----------- data/examples/orders/basic.json | 106 +-- data/quickfort/aliases-common.txt | 37 +- dfhack.init-example | 1 + docs/changelog.txt | 11 +- docs/guides/quickfort-alias-guide.rst | 42 +- docs/guides/quickfort-user-guide.rst | 13 +- 7 files changed, 735 insertions(+), 632 deletions(-) diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index 07aeb09a0..9ac0f58d2 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -59,8 +59,7 @@ quickfort run library/dreamfort.csv -n /setup,# Run before making any manual adj "quickfort orders library/dreamfort.csv -n ""/surface2, /farming2, /surface3, /farming3, /industry2, /surface4""","# Queue up orders required to get the fort minimally functional and secure. You can remove the order for the anvil (you brought one with you, right?)." "" -- Find a good starting spot on the surface -- -quickfort run library/dreamfort.csv -n /perimeter,# Run at embark. -quickfort undo library/dreamfort.csv -n /perimeter,# Clean up after you find your center tile. +gui/quickfort library/dreamfort.csv -n /perimeter,# Run at embark. Don't actually apply the blueprint. Just use the preview shadow to find a good spot on the surface. "" -- Dig -- quickfort run library/dreamfort.csv -n /surface1,# Run when you find your center tile. @@ -91,14 +90,14 @@ orders import furnace,# Automated production of basic furnace-related items. Don "quickfort run,orders library/dreamfort.csv -n /suites2",# Run when the suites level has been dug out. "quickfort run,orders library/dreamfort.csv -n /surface8","# Run if/when you need longer trap corridors on the surface for larger sieges, anytime after you run /surface7." "quickfort run,orders library/dreamfort.csv -n /apartments2",# Run when the first apartment level has been dug out. -"quickfort run,orders library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed on the first apartments level. "quickfort run,orders library/dreamfort.csv -n /services3","# Run after the dining table and chair, weapon rack, and archery targets have been constructed. Also wait until after you complete /surface7, though, because surface defenses are more important than a grand dining hall." "quickfort run,orders library/dreamfort.csv -n /guildhall2",# Run when the guildhall level has been dug out. -"quickfort run,orders library/dreamfort.csv -n /guildhall3",# Optionally run after /guildhall2 to build default furnishings. +"quickfort run,orders library/dreamfort.csv -n ""/guildhall3, /guildhall4""",# Optionally run after /guildhall2 to build default furnishings and declare a library and temple. +"quickfort run,orders library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed on the first apartments level. "quickfort run,orders library/dreamfort.csv -n /farming4",# Run once you have a cache of potash. orders import military,# Automated production of military equipment. Turn on automelt in the meltables piles on the industry level to automatically upgrade all metal military equipment to masterwork quality. These orders are optional if you are not using a military. orders import smelting,# Automated production of all types of metal bars. -"quickfort run,orders library/dreamfort.csv -n /services4","# Run when you need a jail and/or fancy statues in the dining room, anytime after the restraints are placed from /services3." +"quickfort run,orders library/dreamfort.csv -n /services4","# Run when you need a jail, anytime after the restraints are placed from /services3." orders import rockstock,# Maintains a small stock of all types of rock furniture. orders import glassstock,# Maintains a small stock of all types of glass furniture and parts (only import if you have sand). "" @@ -326,7 +325,7 @@ Here are some tips and procedures for handling seiges -- including how to clean "" "- Hauler dwarves will come and release prisoners one by one. Your military dwarves will immediately pounce on the released prisoner and chop them to bits, saving the hauler dwarves from being attacked. Repeat until all prisoners have been ""processed""." "" -"Only types of creatures that you commonly see in sieges are accepted by the prisoner hauling route by default. You can add additional creature types by configuring the hauling route stop in the 'h' menu. Note that generated creature types, like necromancer experiments, can't be explicitly added. You have to (at least temporarily) accept all animals to include them." +"Only common hostile creatures are accepted by the prisoner hauling route by default. You can add additional creature types by configuring the hauling route stop in the 'h' menu. Note that generated creature types, like necromancer experiments, can't be explicitly added. You have to (at least temporarily) accept all animals to include them." #meta label(perimeter) start(central stairs) message(Run quickfort undo on this blueprint to clean up.) show the eventual perimeter of the surface fort; useful for location scouting walls/surface_walls corridor/surface_corridor @@ -383,6 +382,7 @@ roof3/surface_roof3 roof4/surface_roof4 "" #meta label(surface8) start(central stairs) message(Remember to enqueue manager orders for this blueprint.) build extended trap corridors +corridor_gates/surface_corridor_gates corridor/surface_corridor corridor_traps/surface_corridor_traps query_corridor/surface_query_corridor @@ -944,10 +944,9 @@ Remember to connect the levers to the gates once they are built.) gates, barrack ,,,,,,,,,,,,,,,gw,gw,gw,gw,gw,gw,gw #aliases -permit_prisoner: {animalsprefix}{Right}s{search}&&^ -prisoner_route_enable: {enableanimals}{cages}{permittraps}{permit_prisoner search=dwarves}{permit_prisoner search=elves}{permit_prisoner search=humans}{permit_prisoner search=giants}{permit_prisoner search=goblins}{permit_prisoner search=ettins}{permit_prisoner search=cyclopes} +"prisoner_route_enable: {enableanimals}{cages}{permittraps}{animalsprefix}{Right}{permitsearch search="" men""}{permitsearch search=dwarves}{permitsearch search=elves}{permitsearch search=humans}{permitsearch search=kobolds}{permitsearch search=gremlins}{permitsearch search=giants}{permitsearch search=goblins}{permitsearch search=ettins}{permitsearch search=cyclopes}{permitsearch search=ogres}{permitsearch search=eyes}{permitsearch search=reachers}{permitsearch search=gorlaks}{permitsearch search=trolls}{permitsearch search=minotaurs}^" -"#query label(surface_query) start(19; 19) hidden() message(Remember to assign minecarts to the trade goods and prisoner processing quantum stockpiles. +"#query label(surface_query) start(19; 19) hidden() message(Remember to assign minecarts to the trade goods and prisoner processing quantum stockpiles (run ""assign-minecarts all""). Feel free to adjust the configuration of the ""trade goods"" feeder stockpile so it accepts the item types you want to trade away. If those items types are also accepted by other stockpiles, configure those stockpiles to give to the ""trade goods"" stockpile. You might also want to set the ""trade goods quantum"" stockpile to Auto Trade if you have the autotrade DFHack plugin enabled.)" @@ -975,8 +974,8 @@ You might also want to set the ""trade goods quantum"" stockpile to Auto Trade i ,,,`,,`,nocontainers,crafts,,,,,,,`,,,,"{givename name=""inner main gate""}",,,,`,,,,,,,,,`,,` ,,,`,,`,"{givename name=""trade goods""}",,,,,,,,,,,,,,,,,,,,,,,,,`,,` ,,,`,,`,{forbidmasterworkfinishedgoods}{forbidartifactfinishedgoods},,,,,,,,"{givename name=""trade depo gate""}",,,,,,,,"{givename name=""barracks gate""}",,,,,,,,,`,,` -,,,`,,`,,"{quantumstopfromnorth name=""Trade Goods quantum""}",,,,,,,,,,,,,,,,,"{quantumstop name=""Prisoner quantum"" move={Up 5} move_back={Down 5} route_enable={prisoner_route_enable}}{givename name=""prisoner dumper""}",,,,,,,`,,` -,,,`,,`,,"{quantum name=""trade goods quantum""}",,,,,,,`,,,,,,,,`,,"{quantum name=""prisoner quantum"" quantum_enable={enableanimals}}",,,,,,,`,,` +,,,`,,`,,"{quantumstopfromnorth name=""Trade Goods quantum""}",,,,,,,,,,,,,,,,,"{quantumstop name=""Prisoner/Cage quantum"" move={Up 5} move_back={Down 5} route_enable={prisoner_route_enable}}{givename name=""prisoner/cage dumper""}",,,,,,,`,,` +,,,`,,`,,"{quantum name=""trade goods quantum""}",,,,,,,`,,,,,,,,`,,"{quantum name=""prisoner/cage quantum"" quantum_enable={enableanimals}}",,,,,,,`,,` ,,,`,,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,,` ,,,`,,"{givename name=""left outer gate""}",,,,,,,,,"{givename name=""left inner gate""}",,,,,,,,"{givename name=""right inner gate""}",,,,,,,,,"{givename name=""right outer gate""}",,` ,,,`,`,`,`,`,`,`,`,`,`,`,`,,,,"{givename name=""outer main gate""}",,,,`,`,`,`,`,`,`,`,`,`,`,` @@ -1289,11 +1288,45 @@ t1(37x33) -#build label(surface_corridor) start(19; 19) hidden() trap hallway walls +#build label(surface_corridor_gates) start(19; 19) hidden() gates for the longer trap hallways + + +,,,,gx,,,,,,,,,,,,,,,,,,,,,,,,,,,,gx +,,,`,gx,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,gx,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,`,`,`,`,,` +,,,`,,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,`,Tl,`,`,`,Tl,`,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,,` +,,,`,,`,`,`,`,`,`,`,,`,`,`,`,,`,,`,`,`,`,,`,`,`,`,`,`,`,,` +,,,`,,`,,,,,,,,,`,,,,,,,,`,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,`,,,,,,,,`,,,,,,,,,`,,` +,,,`,,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,,` +,,,`,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,` +,,,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,` + + +#build label(surface_corridor) start(19; 19) hidden() longer trap hallway walls -,,,Cw,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,Cw +,,,,~,,,,,,,,,,,,,,,,,,,,,,,,,,,,~ +,,,Cw,~,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,~,Cw ,,,Cw,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,Cw ,,,Cw,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,Cw ,,,Cw,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,Cw @@ -1308,7 +1341,7 @@ t1(37x33) ,,,Cw,,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,`,`,`,`,,Cw ,,,Cw,,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,,Cw ,,,Cw,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,Cw -,,,Cw,,`,,,,,,,,,,`,,`,`,`,,`,,,,,,,,,,`,,Cw +,,,Cw,,`,,,,,,,,,,`,~,`,`,`,~,`,,,,,,,,,,`,,Cw ,,,Cw,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,Cw ,,,Cw,,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,,Cw ,,,Cw,,`,`,`,`,`,`,`,,`,`,`,`,,`,,`,`,`,`,,`,`,`,`,`,`,`,,Cw @@ -1357,11 +1390,11 @@ t1(37x33) -"#build label(surface_corridor_traps) start(19; 19) hidden() barracks, longer trap hallways, and outer levers/gates" +#build label(surface_corridor_traps) start(19; 19) hidden() traps for the longer trap hallways -,,,,gx,,,,,,,,,,,,,,,,,,,,,,,,,,,,gx -,,,`,gx,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,gx,` +,,,,~,,,,,,,,,,,,,,,,,,,,,,,,,,,,~ +,,,`,~,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,~,` ,,,`,Tc,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,Tc,` ,,,`,Tc,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,Tc,` ,,,`,Tc,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,Tc,` @@ -1376,7 +1409,7 @@ t1(37x33) ,,,`,Tc,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,`,`,`,`,Tc,` ,,,`,Tc,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,Tc,` ,,,`,Tc,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,Tc,` -,,,`,Tc,`,,,,,,,,,,`,Tl,`,`,`,Tl,`,,,,,,,,,,`,Tc,` +,,,`,Tc,`,,,,,,,,,,`,~,`,`,`,~,`,,,,,,,,,,`,Tc,` ,,,`,Tc,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,Tc,` ,,,`,Tc,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,Tc,` ,,,`,Tc,`,`,`,`,`,`,`,,`,`,`,`,,`,,`,`,`,`,,`,`,`,`,`,`,`,Tc,` @@ -1449,7 +1482,7 @@ Workshops: "" Manual steps you have to take: - Check to make sure the lower office is assigned to your manager and assign the upper office to your bookkeeper (if different from your manager) -- Assign a minecart to your refuse quantum stockpile hauling route +"- Assign a minecart to your refuse quantum stockpile hauling route (you can run ""assign-minecarts all"" at the DFHack prompt to do this)" "- If the industry level is already built, configure the jugs, pots, and bags stockpiles to take from the ""Goods"" quantum stockpile (the one on the left) on the industry level" "" Farming Walkthrough: @@ -1572,7 +1605,7 @@ build3/farming_build3 "#query label(farming_query_stockpiles) start(16; 18) hidden() message(remember to: -- assign a minecart to the refuse quantum stockpile +- assign a minecart to the refuse quantum stockpile (run ""assign-minecarts all"") - if the industry level is already built, configure the jugs, pots, and bags stockpiles to take from the ""Goods"" quantum stockpile on the industry level) config stockpiles" @@ -1588,7 +1621,7 @@ build3/farming_build3 ,,,,,,`,`,`,"{givename name=""pots""}",,`,,seeds,nocontainers,"{givename name=""seeds feeder""}",give2up,`,,`,,`,`,` ,,,bags,,,`,`,`,`,,`,,`,`,`,`,`,,` ,,`,nocontainers,"{givename name=""bags""}",,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,,`,`,` -,,`,jugs,,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,` +,,`,woodentools,,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,` ,,,nocontainers,"{givename name=""jugs""}",,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,` ,,,,`,,,`,,,,,,`,`,`,`,`,,,,,`,,,,` ,,plants,,,`,`,`,`,`,`,`,,`,`,~,`,`,,`,`,`,,,,,,,"{givename name=""cookable food""}" @@ -1801,7 +1834,7 @@ Manual steps you have to take: "" Optional manual steps you can take: - Restrict the Mechanic's workshop to only allow skilled workers so unskilled trap-resetters won't be tasked to build mechanisms. -"- Restrict the Craftsdwarf's workshops to only allow labors that take from the adjacent stockpiles. That is, only allow Woodcrafting for the Craftsdwarf's workshop on the left near the wood stockpile, Stonecrafting for the Craftsdwarf's workshop near the Mason's workshops, and Bonecrafting for one of the Craftsdwarf's workshop near the Clothier's workshop. The last Craftdwarf's workshop can hold all the remaining labors, or it can be a secondary workshop for a labor that you want more dwarves working on." +"- Restrict the Craftsdwarf's workshops to only allow labors that take from the adjacent stockpiles. That is, only allow Woodcrafting for the Craftsdwarf's workshop on the left near the wood stockpile, Stonecrafting and Strand Extraction for the Craftsdwarf's workshop near the Mason's workshops, and Bonecrafting for one of the Craftsdwarf's workshop near the Clothier's workshop. The last Craftdwarf's workshop can hold all the remaining labors, or it can be a secondary workshop for a labor that you want more dwarves working on." "- To encourage your masons to concentrate on building blocks and other high-volume orders, it helps to set one of your Mason's workshops to service a maximum of 2 manager orders at a time." "- Once you have enough haulers, you can increase the rate of stone and ore hauling jobs by building more wheelbarrows and adding them to the stone and ore feeder stockpiles." "- If desired, set one or both stockpiles in the bottom left to auto-melt. This results in melting all weapons and armor that are inferior to masterwork. This is great for upgrading your military, but it takes a *lot* of fuel unless you have first replaced the forge and smelters with magma versions. If you enable automelt and you don't have magma forges and magma smelters, be sure to be in a heavily forested area, enable auto-chop, and keep your coal stocks high." @@ -1912,13 +1945,13 @@ query/industry_query ,,w,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,w,`,`,`,`,`,`,`,`,`,`,`,`,e(5x1),,,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,w,`,`,`,`,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,`,`,`,` -,,w,`,`,`,`,`,`,w(2x5),,fg(3x3),,`,,`,`,`,`,`,,rhlS(5x5),,,~,~,`,`,`,`,`,`,` +,,w,`,`,`,`,`,`,w(2x5),,fg(3x3),,`,,`,`,`,`,`,,frhlS(5x5),,,~,~,`,`,`,`,`,`,` ,,w,`,`,`,`,`,`,~,~,~,~,~,`,`,,,,`,`,~,~,~,~,~,`,`,`,`,`,`,` ,,`,`,`,`,`,c,`,~,~,~,~,~,,`,,`,,`,,~,~,~,~,~,`,r,`,`,`,`,` ,,f3,`,`,`,`,`,`,~,~,u2(3x2),~,~,`,`,,,,`,`,~,~,~,~,~,`,`,`,`,`,`,` ,,f3,`,`,`,`,`,`,~,~,~,~,~,,`,`,`,`,`,,~,~,~,~,~,`,`,`,`,`,`,` ,,f3,`,`,`,`,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,`,`,`,` -,,f3,`,`,`,`,`,`,`,`,`,`,`,`,bnpdz(5x3),,,,~,`,`,`,`,`,`,`,`,`,`,`,`,` +,,f3,`,`,`,`,`,`,`,`,`,`,`,`,bnpdhz(5x3),,,,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,f3,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,f3,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,pd(7x3),,,~,~,~,~,`,`,`,`,s2(5x2),,,~,~,`,`,`,`,`,`,`,`,`,`,` @@ -1933,7 +1966,7 @@ query/industry_query "#query label(industry_query) start(18; 18) hidden() message(remember to: -- assign minecarts to to your quantum stockpile hauling routes +- assign minecarts to to your quantum stockpile hauling routes (use ""assign-minecarts all"") - if the farming level is already built, give from the ""Goods"" quantum stockpile to the jugs, pots, and bags stockpiles on the farming level - if you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt - once you have enough dwarves, run ""orders import basic"" to automate your fort's basic needs (see /industry_help for more info on this file) @@ -1945,7 +1978,7 @@ query/industry_query ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,~,`,`,`,"{quantum name=""stoneworker quantum""}g{Up 3}&",`,`,`,~,`,`,`,`,`,`,`,`,` +,,,,`,`,`,`,`,`,`,`,`,"{givename name=""stone craftsdwarf""}",`,`,`,"{quantum name=""stoneworker quantum""}g{Up 3}&",`,`,`,~,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,"{quantumstop name=""Stoneworker quantum"" sp_links=""{sp_link move={Down} move_back={Up}}{sp_link move=""""{Down 5}"""" move_back=""""{Up 5}""""}""}{givename name=""stoneworker dumper""}",`,`,`,`,`,`,`,`,~,`,`,`,` ,,,,`,`,~,`,`,~,`,`,`,`,`,otherstone,,,,~,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,~,`,"{givename name=""stone feeder""}",~,~,~,~,`,~,`,`,`,`,`,`,`,`,` @@ -1954,13 +1987,13 @@ query/industry_query ,,~,`,~,`,`,~,`,`,~,`,`,`,`,nocontainers,"{givename name=""gem feeder""}",~,~,~,`,`,`,`,~,`,`,~,`,`,~,`,` ,,~,`,`,`,`,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,`,`,`,` ,,~,`,`,`,`,`,`,"{givename name=""wood feeder""}",~,"{givename name=""goods feeder""}",nocontainers,~,,`,`,`,`,`,,craftrefuse,,,,~,`,`,`,`,`,`,` -,,t{Right 5}{Down}&,`,`,`,`,`,`,~,~,{tallow}{permitdye}{permitwax},~,~,`,`,,,,`,`,"{givename name=""cloth/bones feeder""}",~,~,~,~,`,`,`,`,`,`,` -,,`,`,~,`,`,"{quantum name=""goods/wood quantum""}g{Up 13}{Right 10}&","{quantumstop name=""Goods/Wood quantum"" sp_links=""{sp_link move={Right} move_back={Left}}{sp_link move=""""{Right 5}"""" move_back=""""{Left 5}""""}{sp_link move=""""{Down}{Right 5}"""" move_back=""""{Left 5}{Up}""""}""}{givename name=""goods/wood dumper""}",~,~,{forbidcrafts}{forbidgoblets},~,~,,`,,`,,`,,nocontainers,~,~,~,~,"{quantumstopfromwest name=""Clothier/Bones quantum""}{givename name=""cloth/bones dumper""}","{quantum name=""cloth/bones quantum""}",`,`,~,`,` -,,miscliquid,`,`,`,`,`,`,~,~,"{givename name=""furniture feeder""}",~,~,`,`,,,,`,`,~,~,~,~,~,`,`,`,`,`,`,` -,,"{givename name=""miscliquid""}",`,`,`,`,`,`,~,~,forbidsand,~,~,,`,`,`,`,`,,~,~,~,~,~,`,`,`,`,`,`,` +,,t{Right 5}{Down}&,`,`,`,`,`,`,~,~,{tallow}{permitwax},~,~,`,`,,,,`,`,"{givename name=""cloth/bones feeder""}",g{Up 3}{Right 5}&,~,~,~,`,`,`,`,`,`,` +,,`,`,~,`,`,"{quantum name=""goods/wood quantum""}g{Up 13}{Right 10}&","{quantumstop name=""Goods/Wood quantum"" sp_links=""{sp_link move={Right} move_back={Left}}{sp_link move=""""{Right 5}"""" move_back=""""{Left 5}""""}{sp_link move=""""{Down}{Right 5}"""" move_back=""""{Left 5}{Up}""""}""}{givename name=""goods/wood dumper""}",~,~,{forbidcrafts}{forbidgoblets},~,~,,`,,`,,`,,nocontainers,~,~,~,~,"{quantumstopfromwest name=""Clothier/Bones quantum""}{givename name=""cloth/bones dumper""}","{quantum name=""cloth/bones quantum""}g{Up 4}&",`,`,~,`,` +,,miscliquid,`,`,`,`,`,`,~,~,"{givename name=""furniture feeder""}",~,~,`,`,,,,`,`,forbidadamantinethread,~,~,~,~,`,`,`,`,`,`,` +,,"{givename name=""miscliquid""}",`,`,`,`,`,`,~,~,forbidsand,~,~,,`,`,`,`,`,,dye,~,~,~,~,`,`,`,`,`,`,` ,,~,`,`,`,`,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,`,`,`,` -,,~,`,~,`,`,~,`,`,~,`,`,`,`,forbidpotash,nocontainers,"{givename name=""bar/military feeder""}",~,~,`,`,`,`,~,`,`,~,`,`,`,`,` -,,~,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` +,,~,`,"{givename name=""wood craftsdwarf""}",`,`,~,`,`,~,`,`,`,`,forbidpotash,nocontainers,"{givename name=""bar/military feeder""}",~,~,`,`,`,`,"{givename name=""misc craftsdwarf""}",`,`,"{givename name=""bone craftsdwarf""}",`,`,`,`,` +,,~,`,`,`,`,`,`,`,`,`,`,`,`,adamantinethread,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,~,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,nocontainers,t{Right 12}{Up 3}&,t{Right 11}{Down 3}&,"{givename name=""meltable steel/brnze""}",,,,`,`,~,`,forbidotherstone,,,,,`,~,`,`,`,`,`,`,`,`,` ,,,,{bronzeweapons}{permitsteelweapons}{forbidmasterworkweapons}{forbidartifactweapons},,,,,,,`,`,`,`,"{givename name=""ore/clay feeder""}",~,~,~,~,`,`,`,`,`,`,`,`,`,`,` @@ -1978,7 +2011,7 @@ query/industry_query Screenshot: https://drive.google.com/file/d/13vDIkTVOZGkM84tYf4O5nmRs4VZdE1gh "" Features: -- Spacious dining room (also usable as a tavern) +- Spacious dining room/tavern (tavern is restricted to residents-only by default) - Prepared food and drink stockpiles - Well cistern system (bring your own water) - Hospital with a well for washing @@ -1988,9 +2021,10 @@ Features: Note the hospital also has animal training enabled so it can be used with the dwarfvet plugin if it's enabled. "" Manual steps you have to take: -"- If you want to declare the dining room as a tavern, the bedrooms at the top can be assigned to the tavern as rented rooms." +"- If you want to tavern to attract visitors, change the restriction in the (l)ocation menu." "- Fill the cisterns with water, either with a bucket brigade or by plumbing flowing water. Fill so that there are two z-levels of 7-depth water to prevent muddiness. If you want to fill with buckets, designate a pond zone on the level below the main floor. If you feel adventurous and are experienced with water pressure, you can instead route (depressurized!) water to the second-to-bottom level (the one above the up staircases)." -"- If you are filling the wells with a bucket brigade and at least one well is already constructed, you'll run into issues with the dwarves stealing water from the wells to fill the wells. Temporarily deconstruct the wells or temporarily build grates beneath the wells to block the buckets to avoid this issue. Remember to rebuild the wells or remove the grates afterwards, though!" +"- If you are filling the wells with a bucket brigade and at least one well is already constructed, you'll run into issues with the dwarves stealing water from one well to fill another. Temporarily deconstruct the wells or temporarily build grates beneath the wells to block the buckets to avoid this issue. Remember to rebuild the wells or remove the grates afterwards, though!" +- Assign the office to the right of the barracks to your Sheriff/Captain of the Guard to use it as an interrogation room. "" Services Walkthrough: 1) Start this level when your fort grows to about 50 dwarves so everyone has a place to eat. @@ -1999,82 +2033,78 @@ Services Walkthrough: "" "3) Once the area is dug out, set up important furniture, stockpiles, hospital zone and garbage dump zone with /services2. Run ""quickfort orders"" for /services2." "" -"4) When the table and chair have been placed in the dining room and the weapon rack and archery targets have been constructed in the barracks, run /services3 to build the rest of the furniture and configure your dining room and barracks. Run ""quickfort orders"" for /services3." +"4) When the table and chair have been placed in the dining room, the beds are placed in the rented rooms, and the weapon rack and archery targets are constructed in the barracks, run /services3 to build the rest of the furniture and configure your dining room/tavern and barracks. Run ""quickfort orders"" for /services3." "" 5) Fill the wells with either bucket brigades or by carefully routing flowing water. "" -"6) When your fort is mature enough to need jail cells, run /services4 to set those up -- anytime after the restraints are built in the jail cell block. You also get some decorative statues to increase the value of your dining hall. Run ""quickfort orders"" for /services4." -"#dig label(services1) start(23; 22; central stairs) message(Once the area is dug out, continue with /services2.)" - - -,,,,d,d,d,,,d,d,d,,,d,d,d -,,,,d,d,d,,,d,d,d,,,d,d,d -,,,,d,d,d,,,d,d,d,,,d,d,d -,,,,,d,,,,,d,,,,,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,d,h,d,,j,,d,h,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,,d,,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,,,,,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,d,h,d,,d,,d,h,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,,d,,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,,,,,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,,,,d,,d,,,,,d,d,d,d,d,d,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,,,d,d,d,d,d,,,,d,d,d,d,d,d,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,`,`,d,d,d,d,d,d,d,d,d,d,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,d,,d,`,`,`,d,,d,,d,d,d,d,h,d,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,`,`,d,d,d,d,d,d,d,d,d,d,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,,,d,d,d,d,d,,,,d,d,d,d,d,d,d,d,d -,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,,,,,,d,,,,,,d,d,d,d,d,d,d,d,d -,,,,,,,,d,d,,d,d,,,,,,,,,,,,,,,,d,d,d,d,d,d,d,d,d -,,,,d,d,d,d,d,d,,d,d,d,d,d,d,,,,,,,,,,,,d,d,d,d,d,d,d,d,d -,,,,d,d,d,d,d,d,,d,d,d,d,d,d -,,,,d,d,d,d,d,d,,d,d,d,d,d,d -,,,,d,d,d,d,d,d,,d,d,d,d,d,d -,,,,d,d,d,d,d,d,,d,d,d,d,d,d -#> - - - - - - -,,,,,,,,,,,,,,,,,,j5,h5,j,,u,,j,h5,j5 -,,,,,,,,,,,,,,,,,,5,5,5,5,d,5,5,5,5 -,,,,,,,,,,,,,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,j5,h5,j,,d,,j,h5,j5 -,,,,,,,,,,,,,,,,,,5,5,5,5,d,5,5,5,5 -,,,,,,,,,,,,,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,,,d,d,d,d,d,d,d,d,d,d,d -,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,,,d,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,d,d,d,d,d,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,,5,5,5 -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,,j,h5,j5 -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d -,,,,,,,,,,,,,,,,,,,,d,d,d,d,d - +"6) When your fort is mature enough to need jail cells, run /services4 to set those up, anytime after the restraints are built in the jail cell block. You also get some decorative statues to increase the value of your dining hall. Assign the office to the right of the barracks to your Sheriff/Captain of the Guard to use it as an interrogation room. Run ""quickfort orders"" for /services4." +"#dig label(services1) start(18; 18; central stairs) message(Once the area is dug out, continue with /services2.)" + +,d,d,d,,d,d,d,,d,d,d,,d,h,d,,j,,d,h,d +,d,d,d,,d,d,d,,d,d,d,,d,d,d,d,d,d,d,d,d +,d,d,d,,d,d,d,,d,d,d,,d,d,d,,d,,d,d,d +,,d,,,,d,,,,d,,,,,,,d +,d,d,d,d,d,d,d,d,d,d,d,,d,h,d,,d,,d,h,d,,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,,d,,d,d,d,,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d,,,,,,d,,,,,,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,,,,d +,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,,d,,d,,d,,d +,d,d,d,d,d,d,d,d,d,d,d,,,,,d,,d,,,,,d,,d,,d,,d +,d,d,d,d,d,d,d,d,d,d,d,,,,d,d,d,d,d,,,,d,d,d,d,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,`,`,d,d,d,d,d,d,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d,,d,,d,`,`,`,d,,d,,d,d,d,h,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,`,`,d,d,d,d,d,d,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d,,,,d,d,d,d,d,,,,d,d,d,d,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d,,,,,,d,,,,,,d,,d,,d,,d +,d,d,d,d,d,d,d,d,d,d,d,,,,,,,,,,,,d,,d,,d,,d +,d,d,d,d,d,d,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d +,d,d,d,d,d,d,d,d,d,d,d +,,,,d,d,,d,d +,d,d,d,d,d,,d,d,d,d,d +,d,d,d,d,d,,d,d,d,d,d +,d,d,d,d,d,,d,d,d,d,d +,d,d,d,d,d,,d,d,d,d,d +,d,d,d,d,d,,d,d,d,d,d #> +,,,,,,,,,,,,,j5,h5,j,,u,,j,h5,j5 +,,,,,,,,,,,,,5,5,5,5,d,5,5,5,5 +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,j5,h5,j,,d,,j,h5,j5 +,,,,,,,,,,,,,5,5,5,5,d,5,5,5,5 +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,,,,,d,d,d,d,d,d,d,d,d,d +,,,,,,,,,,,,,,,,,6,,,,,,,,,d +,,,,,,,,,,,,,,,,,d,,,,,,,,,d +,,,,,,,,,,,,,,,d,d,d,d,d,,,,,,,d +,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,5,5,5 +,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,j,h5,j5 +,,,,,,,,,,,,,,,d,`,`,`,d +,,,,,,,,,,,,,,,d,d,d,d,d +#> +,,,,,,,,,,,,,u5,h5,i,,,,i,h5,u5 -,,,,,,,,,,,,,,,,,,u5,h5,i,,,,i,h5,u5 - +,,,,,,,,,,,,,u5,h5,i,,,,i,h5,u5 -,,,,,,,,,,,,,,,,,,u5,h5,i,,,,i,h5,u5 @@ -2082,38 +2112,33 @@ Services Walkthrough: +,,,,,,,,,,,,,,,d,d,d,d,d +,,,,,,,,,,,,,,,d,`,`,`,d +,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,i,h5,u5 +,,,,,,,,,,,,,,,d,`,`,`,d +,,,,,,,,,,,,,,,d,d,d,d,d -,,,,,,,,,,,,,,,,,,,,d,d,d,d,d -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,,i,h5,u5 -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d -,,,,,,,,,,,,,,,,,,,,d,d,d,d,d #> +,,,,,,,,,,,,,,d,u,,,,u,d - - -,,,,,,,,,,,,,,,,,,,d,u,,,,u,d +,,,,,,,,,,,,,,d,u,,,,u,d -,,,,,,,,,,,,,,,,,,,d,u,,,,u,d - - -,,,,,,,,,,,,,,,,,,,,d,d,d,d,d -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,,u,d -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d -,,,,,,,,,,,,,,,,,,,,d,d,d,d,d - +,,,,,,,,,,,,,,,d,d,d,d,d +,,,,,,,,,,,,,,,d,`,`,`,d +,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,u,d +,,,,,,,,,,,,,,,d,`,`,`,d +,,,,,,,,,,,,,,,d,d,d,d,d "#meta label(services2) start(central stairs) message(Remember to enqueue manager orders for this blueprint. Once furniture has been placed, continue with /services3.) dining hall anchors, stockpiles, hospital, garbage dump" @@ -2123,446 +2148,490 @@ zones/services_zones name_zones/services_name_zones query_stockpiles/services_query_stockpiles "" -"#meta label(services3) start(central stairs) message(Remember to enqueue manager orders for this blueprint.) configure dining room, build dining hall and hospital furniture" +"#meta label(services3) start(central stairs) message(Remember to enqueue manager orders for this blueprint.) configure dining room/tavern, build dining hall and hospital furniture" query_dining/services_query_dining +query_rented_rooms/services_query_rented_rooms build2/services_build2 "" -#meta label(services4) start(central stairs) message(Remember to enqueue manager orders for this blueprint.) complete jail and build decorative furniture +#meta label(services4) start(central stairs) message(Remember to enqueue manager orders for this blueprint.) declare and furnish jail and build decorative furniture build3/services_build3 place_jail/services_place_jail query_jail/services_query_jail -#build label(services_build) start(23; 22) hidden() build basic hospital and dining room anchor - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,d -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,A,A,A,`,A,A,A,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,r,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,b,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,h,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,d,,d,,,,,`,`,`,`,`,`,t,`,` -,,,,`,`,`,`,t,c,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,d,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,R -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,l,`,`,`,R -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,d,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,R -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,h,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - - -#place label(services_place) start(23; 22) hidden() - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,z(7x2),`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,f(6x5),`,`,`,`,`,,f(6x5),`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - - -"#zone label(services_zones) start(23; 22) hidden() message(If you'd like to fill your wells via bucket brigade, activate the inactive pond zones one level down from where the wells will be built.) hospital, garbage dump, and pond zones" - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,ht(9x11),`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,d,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -#> - +#build label(services_build) start(18; 18) hidden() build basic hospital and dining room anchor + +,b,b,b,,b,b,b,,b,b,b,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,d,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,A,A,A,`,A,A,A,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,r,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,d,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,b +,`,`,`,`,`,`,`,`,`,`,`,,,,,d,,d,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,t,`,`,`,`,R +,`,`,`,`,`,`,`,`,`,`,`,d,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,h +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,l,`,`,`,`,R +,`,`,`,`,`,`,`,`,`,`,`,d,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,t,c,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,R +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,h,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +#place label(services_place) start(18; 18) hidden() + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,z(7x3),,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,c,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,f(5x5),,,`,`,,f(5x5),,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +"#zone label(services_zones) start(18; 18) hidden() message(If you'd like to fill your wells via bucket brigade, activate the inactive pond zones one level down from where the wells will be built.) hospital, garbage dump, and pond zones" + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,ht,,ht,,ht,,ht +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,ht,,ht,,ht,,ht +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,ht,ht,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,ht,ht,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,ht,ht,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,`,,,,,,d,,,,,,ht,,ht,,ht,,ht +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,ht,,ht,,ht,,ht +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` - - - - -,,,,,,,,,,,,,,,,,,`,apPf,`,,`,,`,apPf,` -,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,`,apPf,`,,`,,`,apPf,` -,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,`,apPf,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,` - - -#query label(services_name_zones) start(23; 22) hidden() - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,"{namezone name=""hospital""}" -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,"{namezone name=""garbage dump""}",,,,,,,,,,,,,,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` #> - - - - - -,,,,,,,,,,,,,,,,,,`,"{namezone name=""jail3 well""}",`,,`,,`,"{namezone name=""jail4 well""}",` -,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,`,"{namezone name=""jail1 well""}",`,,`,,`,"{namezone name=""jail2 well""}",` -,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,`,"{namezone name=""hospital well""}",` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,` - - -#query label(services_query_stockpiles) start(23; 22) hidden() message(Configure the training ammo stockpile to take from the metalworker quantum on the industry level.) configure stockpiles - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,nocontainers,{bolts}{forbidmetalbolts}{forbidartifactammo},"{givename name=""training bolts""}",`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,preparedfood,"{givename name=""prepared food""}",`,`,`,`,,booze,"{givename name=""booze""}",`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - - -#query label(services_query_dining) start(23; 22) message(the bedrooms above the tavern are left unconfigured so you can add them as rented rooms) set up dining room and barracks - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,r&w,r&w,r&w,`,r&w,r&w,r&w,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,r+++&,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,"r{+ 11}&h{givename name=""grand hall""}",,,,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - - -"#build label(services_build2) start(23; 22) hidden() build rest of hospital and dining room, doors, prep for jail" - - -,,,,b,b,b,,,b,b,b,,,b,b,b -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,f,`,h,,,f,`,h,,,f,`,h -,,,,,d,,,,,d,,,,,d -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,v,`,d,`,d,`,v,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,v,`,d,`,d,`,v,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,~ -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,b,~,~,~,`,~,~,~,b -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,b,`,`,`,~,`,`,`,b -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,b,`,`,`,a,`,`,`,b,,f,`,`,`,`,b,~,b,b -,,,,`,`,c,t,t,c,`,c,t,t,c,`,`,,h,`,`,`,`,`,`,`,h,,~,`,`,`,`,`,`,`,b -,,,,`,`,c,t,t,c,`,c,t,t,c,`,`,,,,,~,,~,,,,,f,`,`,`,`,`,~,`,b -,,,,`,`,c,t,~,~,`,c,t,t,c,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,c,t,t,c,`,c,t,t,c,`,`,~,`,d,`,`,`,`,`,d,`,d,`,`,`,`,`,`,`,`,~ -,,,,`,`,c,t,t,c,`,c,t,t,c,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,~ -,,,,`,`,c,t,t,c,`,c,t,t,c,`,`,~,`,d,`,`,`,`,`,d,`,d,`,`,`,`,`,`,`,`,~ -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,h,`,`,~,`,`,`,`,`,h,`,`,h,,,,,,`,,,,,,f,`,`,`,`,`,t,`,b -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,h,`,`,`,`,`,`,`,b -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,f,`,`,`,`,b,b,b,b -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - +,,,,,,,,,,,,,`,apPf,`,,`,,`,apPf,` +,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,`,apPf,`,,`,,`,apPf,` +,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,`,,,,,,,,,` +,,,,,,,,,,,,,,,,,`,,,,,,,,,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,`,apPf,` +,,,,,,,,,,,,,,,`,`,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,` + +#query label(services_name_zones) start(18; 18) hidden() + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,"{namezone name=""hospital""}" +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,"{namezone name=""garbage dump""}" +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` #> +,,,,,,,,,,,,,`,"{namezone name=""jail3 well""}",`,,`,,`,"{namezone name=""jail4 well""}",` +,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,`,"{namezone name=""jail1 well""}",`,,`,,`,"{namezone name=""jail2 well""}",` +,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,`,,,,,,,,,` +,,,,,,,,,,,,,,,,,`,,,,,,,,,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,`,"{namezone name=""hospital well""}",` +,,,,,,,,,,,,,,,`,`,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,` + +#query label(services_query_stockpiles) start(18; 18) hidden() message(Configure the training ammo stockpile to take from the metalworker quantum on the industry level.) configure stockpiles + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,nocontainers,{bolts}{forbidmetalbolts}{forbidartifactammo},"{givename name=""training bolts""}",`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,"{givename name=""garbage dump""}" +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,preparedfood,"{givename name=""prepared food""}",`,`,`,,booze,"{givename name=""booze""}",`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +"#query label(services_query_dining) start(18; 18) message(The tavern is restricted to residents only by default. If you'd like your tavern to attract vistors, please go to the (l)ocation menu and change the restriction.) set up dining room/tavern and barracks" + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,r+&w,r+&w,r+&w,`,r+&w,r+&w,r+&w,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,r{+ 2}&,,,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,"r{+ 12}&h{givename name=""grand hall""}lai^l{Up}r^q",,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +#query label(services_query_rented_rooms) start(18; 18) attach rented rooms to tavern + +,r&l-&,r&l-&,r&l-&,,r&l-&,r&l-&,r&l-&,,r&l-&,r&l-&,r&l-&,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +"#build label(services_build2) start(18; 18) hidden() build rest of hospital and dining room, doors, prep for jail" + +,~,~,~,,~,~,~,,~,~,~,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,v,`,d,`,d,`,v,` +,f,`,h,,f,`,h,,f,`,h,,`,`,`,,`,,`,`,` +,,d,,,,d,,,,d,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,v,`,d,`,d,`,v,`,,`,`,c,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,~,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,b,~,~,~,`,~,~,~,h,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,b,`,`,`,~,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,b,`,`,`,`,`,`,`,`,~,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,b,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,b,`,`,`,`,`,`,`,a,,b,,b,,b,,~ +,`,`,`,`,`,`,`,`,`,`,`,,,,,~,,~,,,,,d,,d,,d,,d +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,~,`,`,`,d,~ +,`,c,t,t,c,`,c,t,t,c,`,~,`,d,`,`,`,`,`,d,`,d,`,`,`,`,`,`,~ +,`,c,t,t,c,`,c,t,t,c,`,,`,,`,`,`,`,`,,`,,`,`,`,~,`,`,`,d,~ +,`,c,t,t,c,`,c,t,t,c,`,~,`,d,`,`,`,`,`,d,`,d,`,`,`,`,`,`,f +,`,c,t,~,~,`,c,t,t,c,`,,,,`,`,`,`,`,,,,`,`,`,t,`,`,`,d,~ +,`,c,t,t,c,`,c,t,t,c,`,,,,,,`,,,,,,d,,d,,d,,d +,`,c,t,t,c,`,c,t,t,c,`,,,,,,,,,,,,b,,b,,b,,b +,`,c,t,t,c,`,c,t,t,c,` +,`,c,t,t,c,`,c,t,t,c,` +,`,c,t,t,c,`,c,t,t,c,` +,`,c,t,t,c,`,c,t,t,c,` +,`,`,`,`,`,`,`,`,`,`,` +,h,`,`,`,`,~,`,`,`,`,h +,,,,d,d,,d,d +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +#> - - - -,,,,,,,,,,,,,,,,,,`,`,`,,`,,`,`,` -,,,,,,,,,,,,,,,,,,`,`,`,d,`,d,`,`,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,`,`,`,,`,,`,`,` -,,,,,,,,,,,,,,,,,,`,`,`,d,`,d,`,`,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,d,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,` - - - - -"#build label(services_build3) start(23; 22) hidden() jail, statues" - - -,,,,~,~,~,,,~,~,~,,,~,~,~ -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,~,`,~,,,~,`,~,,,~,`,~ -,,,,,~,,,,,~,,,,,~ -,,,,s,`,`,s,s,`,`,`,s,s,`,`,s,,t,l,b,,`,,t,l,b -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,c,~,`,~,`,~,c,~,` -,,,,s,`,`,`,`,`,`,`,`,`,`,`,s,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,s,`,`,`,`,`,`,`,`,`,`,`,s,,t,l,b,,`,,t,l,b -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,c,~,`,~,`,~,c,~,` -,,,,s,`,`,`,`,`,`,`,`,`,`,`,s,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,~ -,,,,s,`,`,`,`,`,`,`,`,`,`,`,s,,~,`,`,`,`,`,`,`,~ -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,~,`,`,`,~,`,`,`,~ -,,,,s,`,`,`,`,`,`,`,`,`,`,`,s,,~,`,`,`,~,`,`,`,~,,~,`,s,s,`,~,~,~,~ -,,,,`,`,~,~,~,~,`,~,~,~,~,`,`,,~,`,`,`,`,`,`,`,~,,~,`,`,`,`,`,`,`,~ -,,,,s,`,~,~,~,~,`,~,~,~,~,`,s,,,,,~,,~,,,,,~,`,`,`,`,`,~,`,~ -,,,,`,`,~,~,~,~,`,~,~,~,~,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,s,`,~,~,~,~,`,~,~,~,~,`,`,~,`,~,`,`,`,`,`,~,`,~,`,`,`,`,`,`,`,`,~ -,,,,`,`,~,~,~,~,`,~,~,~,~,`,`,,s,,`,`,`,`,`,,s,,`,`,`,`,~,`,`,`,~ -,,,,s,`,~,~,~,~,`,~,~,~,~,`,`,~,`,~,`,`,`,`,`,~,`,~,`,`,`,`,`,`,`,`,~ -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,~,s,s,~,`,`,`,`,`,~,s,s,~,,,,,,`,,,,,,~,`,`,`,`,`,~,`,~ -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,~,`,`,`,`,`,`,`,~ -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,~,`,s,s,`,~,~,~,~ -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - - -#place label(services_place_jail) start(23; 22) hidden() - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,f(1x2),`,`,`,`,`,f(1x2) -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,f(2x1),`,`,,`,,f(2x1),`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,f(1x2),`,`,`,`,`,f(1x2) -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,f(2x1),`,`,,`,,f(2x1),`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - - -#query label(services_query_jail) start(23; 22) hidden() set up barracks and jail - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,"r--&j{givename name=""jail3""}","{booze}{givename name=""booze""}",`,`,`,`,"r--&j{givename name=""jail4""}","{booze}{givename name=""booze""}" -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,"{preparedfood}{givename name=""prepared food""}",t{Down 4}&,t{Down 4}&,,`,,"{preparedfood}{givename name=""prepared food""}",t{Down 4}&,t{Down 4}& -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,"r--&j{givename name=""jail1""}","{booze}{givename name=""booze""}",`,`,`,`,"r--&j{givename name=""jail2""}","{booze}{givename name=""booze""}" -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,"{preparedfood}{givename name=""prepared food""}",t{Down 14}{Left 10}&,t{Down 14}{Left 4}&,,`,,"{preparedfood}{givename name=""prepared food""}",t{Left 6}&,t{Left 6}& -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - +,,,,,,,,,,,,,`,`,`,,`,,`,`,` +,,,,,,,,,,,,,`,`,`,d,`,d,`,`,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,`,`,`,,`,,`,`,` +,,,,,,,,,,,,,`,`,`,d,`,d,`,`,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,`,,,,,,,,,` +,,,,,,,,,,,,,,,,,d,,,,,,,,,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,d +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,` + +"#build label(services_build3) start(18; 18) hidden() jail, statues" + +,~,~,~,,~,~,~,,~,~,~,,t,l,b,,`,,t,l,b +,`,`,`,,`,`,`,,`,`,`,,c,~,`,~,`,~,c,~,` +,~,`,~,,~,`,~,,~,`,~,,`,`,`,,`,,`,`,` +,,~,,,,~,,,,~,,,,,,,` +,`,`,`,s,`,`,`,s,`,`,`,,t,l,b,,`,,t,l,b,,j,`,`,`,j +,`,`,`,`,`,`,`,`,`,`,`,,c,~,`,~,`,~,c,~,`,,`,`,~,`,` +,s,`,`,`,`,`,`,`,`,`,s,,`,`,`,,`,,`,`,`,,v,`,t,`,v +,`,`,`,`,`,`,`,`,`,`,`,,,,,,~,,,,,,`,`,c,`,` +,s,`,`,`,`,`,`,`,`,`,s,,~,~,~,~,`,~,~,~,~,,j,`,`,`,j +,`,`,`,`,`,`,`,`,`,`,`,,~,`,`,`,~,`,`,`,`,,,,d +,s,`,`,`,`,`,`,`,`,`,s,,~,`,`,`,`,`,`,`,`,~,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,~,`,`,`,`,`,`,`,` +,s,`,`,`,`,`,`,`,`,`,s,,~,`,`,`,`,`,`,`,~,,~,,~,,~,,~ +,`,`,`,`,`,`,`,`,`,`,`,,,,,~,,~,,,,,~,,~,,~,,~ +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,s,`,~,`,`,`,~,~ +,`,~,~,~,~,`,~,~,~,~,`,~,`,~,`,`,`,`,`,~,`,~,`,`,`,`,`,`,~ +,`,~,~,~,~,`,~,~,~,~,`,,s,,`,`,`,`,`,,s,,`,`,`,~,`,`,`,~,~ +,`,~,~,~,~,`,~,~,~,~,`,~,`,~,`,`,`,`,`,~,`,~,`,`,`,`,`,`,~ +,`,~,~,~,~,`,~,~,~,~,`,,,,`,`,`,`,`,,,,`,s,`,~,`,`,`,~,~ +,`,~,~,~,~,`,~,~,~,~,`,,,,,,`,,,,,,~,,~,,~,,~ +,`,~,~,~,~,`,~,~,~,~,`,,,,,,,,,,,,~,,~,,~,,~ +,`,~,~,~,~,`,~,~,~,~,` +,`,~,~,~,~,`,~,~,~,~,` +,`,~,~,~,~,`,~,~,~,~,` +,`,~,~,~,~,`,~,~,~,~,` +,`,`,`,`,`,`,`,`,`,`,` +,~,`,s,`,`,~,`,`,s,`,~ +,,,,~,~,,~,~ +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +#place label(services_place_jail) start(18; 18) hidden() + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,f(1x2),`,`,`,`,`,f(1x2) +,`,`,`,,`,`,`,,`,`,`,,f(2x1),`,`,,`,,f(2x1),`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,f(1x2),`,`,`,`,`,f(1x2),,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,f(2x1),`,`,,`,,f(2x1),`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +#query label(services_query_jail) start(18; 18) hidden() message(Assign the office to the right of the barracks to your Sheriff/Captain of the Guard to use it as your interrogation room) set up barracks and jail + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,"r--&j{givename name=""jail3""}","{booze}{givename name=""booze""}",`,`,`,`,"r--&j{givename name=""jail4""}","{booze}{givename name=""booze""}" +,`,`,`,,`,`,`,,`,`,`,,"{preparedfood}{givename name=""prepared food""}",t{Down 4}&,t{Down 4}&,,`,,"{preparedfood}{givename name=""prepared food""}",t{Down 4}&,t{Down 4}& +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,"r--&j{givename name=""jail1""}","{booze}{givename name=""booze""}",`,`,`,`,"r--&j{givename name=""jail2""}","{booze}{givename name=""booze""}",,`,`,"r+&{givename name=""sheriff's office""}",`,` +,`,`,`,`,`,`,`,`,`,`,`,,"{preparedfood}{givename name=""prepared food""}",t{Down 22}{Left 10}&,t{Down 22}{Left 4}&,,`,,"{preparedfood}{givename name=""prepared food""}",t{Left 6}&,t{Left 6}&,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,"{givename name=""interrogation""}" +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` #notes label(guildhall_help) "Eight 7x7 rooms for guildhalls, temples, libraries, etc." Screenshot: https://drive.google.com/file/d/17jHiCKeZm6FSS-CI4V0r0GJZh09nzcO_ "" Features: -"- Big rooms, optionally pre-furnished. Double-thick walls to ensure engravings add value to the ""correct"" side. Declare locations as needed." +"- Big rooms, optionally pre-furnished. Double-thick walls to ensure engravings add value to the ""correct"" side. Declare locations from the pre-created meeting zones as needed." "" Guildhall Walkthrough: 1) Dig out the rooms with /guildhall1. "" "2) Once the area is dug out, pre-create the zones and add doors and a few statues with /guildhall2. Run ""quickfort orders"" for /guildhall2." "" -"3) Furnish individual rooms manually, or get default furnishings for a variety of room types by running /guildhall3. Declare appropriate locations from the pre-created zones as you need guildhalls, libraries, and temples. If you need more rooms, you can dig another /guildhall1 in an unused z-level." +"3) Furnish individual rooms manually, or get default furnishings for a variety of room types by running /guildhall3. If you use the default furnishings, also run ""quickfort orders"" for /guildhall3. Declare appropriate locations from the pre-created zones as you need guildhalls, libraries, and temples. If you'd like a ""no specific diety"" temple declared for the top room and a library declared for the bottom room, run /guildhall4. Both locations will be ""Residents only"" by default, but you can change this in the (l)ocation menu if you want them to attract visitors. If you need more rooms, you can dig another /guildhall1 in an unused z-level." "#dig label(guildhall1) start(15; 15; central stairs) message(Once the area is dug out, continue with /guildhall2.)" @@ -2687,6 +2756,36 @@ Smooth/engrave tiles, furnish rooms, and declare locations as required.) build d ,,`,`,`,`,s,`,`,,,h,~c,~c,s,~c,~c,h,,,`,`,s,`,`,`,` +"#query label(guildhall4) start(15; 15; central stairs) message(The library and temple are restricted to residents only by default. If you'd like them to attract vistors, please go to the (l)ocation menu and change the restriction.) declare a library and temple" + + +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,^ilat&^l{Up}r^q,,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,,,,,,`,,,,,,`,,`,,,,,,` +,,,,,,,`,,,,,,`,`,`,,,,,,` +,,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,`,,`,,`,,`,,`,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,` +,,,,,,,`,,,,,,`,`,`,,,,,,` +,,,,,,,`,,,,,,`,,`,,,,,,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,^ilal&^l{Up}r^q,,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` + + #notes label(beds_help) Suites for nobles and apartments for the teeming masses Suites screenshot: https://drive.google.com/file/d/1IBqCf6fF3lw7sHiBE_15Euubysl5AAiS diff --git a/data/examples/orders/basic.json b/data/examples/orders/basic.json index e8dee1680..4c785e7ab 100644 --- a/data/examples/orders/basic.json +++ b/data/examples/orders/basic.json @@ -563,37 +563,6 @@ "is_active" : false, "is_validated" : false, "item_conditions" : - [ - { - "condition" : "AtLeast", - "flags" : - [ - "collected", - "dyeable" - ], - "item_type" : "THREAD", - "value" : 5 - }, - { - "condition" : "AtLeast", - "flags" : - [ - "unrotten", - "dye" - ], - "value" : 15 - } - ], - "job" : "DyeThread" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 18, - "is_active" : false, - "is_validated" : false, - "item_conditions" : [ { "condition" : "AtLeast", @@ -611,7 +580,7 @@ "unrotten", "dye" ], - "value" : 15 + "value" : 3 } ], "job" : "DyeCloth" @@ -620,7 +589,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 19, + "id" : 18, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -655,20 +624,14 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 20, + "id" : 19, "is_active" : false, "is_validated" : false, "item_conditions" : [ { "condition" : "AtLeast", - "flags" : - [ - "non_economic", - "hard" - ], - "item_type" : "BOULDER", - "material" : "INORGANIC", + "item_type" : "WOOD", "value" : 20 }, { @@ -684,13 +647,16 @@ ], "item_subtype" : "ITEM_TOOL_JUG", "job" : "MakeTool", - "material" : "INORGANIC" + "material_category" : + [ + "wood" + ] }, { "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 21, + "id" : 20, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -719,7 +685,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 22, + "id" : 21, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -747,7 +713,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 23, + "id" : 22, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -775,7 +741,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 24, + "id" : 23, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -805,7 +771,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 25, + "id" : 24, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -835,7 +801,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 26, + "id" : 25, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -864,7 +830,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 27, + "id" : 26, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -895,7 +861,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 28, + "id" : 27, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -931,7 +897,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 29, + "id" : 28, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -967,7 +933,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 30, + "id" : 29, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1003,7 +969,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 31, + "id" : 30, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1022,9 +988,9 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 32, + "id" : 31, "is_active" : false, - "is_validated" : true, + "is_validated" : false, "item_conditions" : [ { @@ -1048,7 +1014,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 33, + "id" : 32, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1070,7 +1036,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 34, + "id" : 33, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1096,7 +1062,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 35, + "id" : 34, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1122,7 +1088,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 36, + "id" : 35, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1148,7 +1114,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 37, + "id" : 36, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1181,7 +1147,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 38, + "id" : 37, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1220,7 +1186,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 39, + "id" : 38, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1254,7 +1220,7 @@ "amount_left" : 4, "amount_total" : 4, "frequency" : "Daily", - "id" : 40, + "id" : 39, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1277,7 +1243,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 41, + "id" : 40, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1301,7 +1267,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 42, + "id" : 41, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1335,7 +1301,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 43, + "id" : 42, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1364,7 +1330,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 44, + "id" : 43, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1393,9 +1359,9 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 45, + "id" : 44, "is_active" : false, - "is_validated" : true, + "is_validated" : false, "item_conditions" : [ { @@ -1424,7 +1390,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 46, + "id" : 45, "is_active" : false, "is_validated" : false, "item_conditions" : diff --git a/data/quickfort/aliases-common.txt b/data/quickfort/aliases-common.txt index e918b28a9..f000b5bd1 100644 --- a/data/quickfort/aliases-common.txt +++ b/data/quickfort/aliases-common.txt @@ -77,6 +77,11 @@ give10right: {give move={Right 10}} togglesequence: &{Down} togglesequence2: &{Down 2} +# these aliases use the DFHack "search" plugin to filter the right column +forbidsearch: s{search}&f{Left}{Right} +permitsearch: s{search}&p{Left}{Right} +togglesearch: s{search}&&{Left}{Right} + masterworkonly: {prefix}{Right}{Up 2}f{Right}{Up 2}&^ artifactonly: {prefix}{Right}{Up 2}f{Right}{Up}&^ @@ -116,7 +121,7 @@ plants: {foodprefix}b{Right}{Down 4}p^ booze: {foodprefix}b{Right}{Down 5}p{Down}p^ seeds: {foodprefix}b{Right}{Down 9}p^ dye: {foodprefix}b{Right}{Down 11}{Right}{Down 28}{togglesequence 4}^ -tallow: {foodprefix}b{Right}{Down 13}{Right}stallow&p^ +tallow: {foodprefix}b{Right}{Down 13}{Right}{permitsearch search=tallow}^ miscliquid: {foodprefix}b{Right}{Down 18}p^ wax: {foodprefix}b{Right}{Down 15}{Right}{Down 6}&^ @@ -126,7 +131,7 @@ forbidplants: {foodprefix}{Right}{Down 4}f^ forbidbooze: {foodprefix}{Right}{Down 5}f{Down}f^ forbidseeds: {foodprefix}{Right}{Down 9}f^ forbiddye: {foodprefix}{Right}{Down 11}{Right}{Down 28}{togglesequence 4}^ -forbidtallow: {foodprefix}{Right}{Down 13}{Right}stallow&f^ +forbidtallow: {foodprefix}{Right}{Down 13}{Right}{forbidsearch search=tallow}^ forbidmiscliquid: {foodprefix}{Right}{Down 18}f^ forbidwax: {foodprefix}{Right}{Down 15}{Right}{Down 6}&^ @@ -136,7 +141,7 @@ permitplants: {foodprefix}{Right}{Down 4}p^ permitbooze: {foodprefix}{Right}{Down 5}p{Down}p^ permitseeds: {foodprefix}{Right}{Down 9}p^ permitdye: {forbiddye} -permittallow: {foodprefix}{Right}{Down 13}{Right}stallow&p^ +permittallow: {foodprefix}{Right}{Down 13}{Right}{permitsearch search=tallow}^ permitmiscliquid: {foodprefix}{Right}{Down 18}p^ permitwax: {forbidwax} @@ -200,7 +205,8 @@ shells: {refuseprefix}b{Right}{Down 5}p^ teeth: {refuseprefix}b{Right}{Down 6}p^ horns: {refuseprefix}b{Right}{Down 7}p^ hair: {refuseprefix}b{Right}{Down 8}p^ -craftrefuse: {skulls}{permitbones}{permitshells}{permitteeth}{permithorns}{permithair} +usablehair: {refuseprefix}b{Right}{Down 8}{Right}{togglesearch search=sheep}{togglesearch search=llama}{togglesearch search=alpaca}{togglesearch search=troll}^ +craftrefuse: {skulls}{permitbones}{permitshells}{permitteeth}{permithorns}{permitusablehair} forbidcorpses: {refuseprefix}{Right}{Down}f^ forbidrawhides: {refuseprefix}{Right 2}{Down}&^ @@ -211,7 +217,8 @@ forbidshells: {refuseprefix}{Right}{Down 5}f^ forbidteeth: {refuseprefix}{Right}{Down 6}f^ forbidhorns: {refuseprefix}{Right}{Down 7}f^ forbidhair: {refuseprefix}{Right}{Down 8}f^ -forbidcraftrefuse: {forbidskulls}{forbidbones}{forbidshells}{forbidteeth}{forbidhorns}{forbidhair} +forbidusablehair: {refuseprefix}{Right}{Down 8}{Right}{forbidsearch search=sheep}{forbidsearch search=llama}{forbidsearch search=alpaca}{forbidsearch search=troll}^ +forbidcraftrefuse: {forbidskulls}{forbidbones}{forbidshells}{forbidteeth}{forbidhorns}{forbidusablehair} permitcorpses: {refuseprefix}{Right}{Down}p^ permitrawhides: {forbidrawhides} @@ -222,7 +229,8 @@ permitshells: {refuseprefix}{Right}{Down 5}p^ permitteeth: {refuseprefix}{Right}{Down 6}p^ permithorns: {refuseprefix}{Right}{Down 7}p^ permithair: {refuseprefix}{Right}{Down 8}p^ -permitcraftrefuse: {permitskulls}{permitbones}{permitshells}{permitteeth}{permithorns}{permithair} +permitusablehair: {refuseprefix}{Right}{Down 8}{Right}{permitsearch search=sheep}{permitsearch search=llama}{permitsearch search=alpaca}{permitsearch search=troll}^ +permitcraftrefuse: {permitskulls}{permitbones}{permitshells}{permitteeth}{permithorns}{permitusablehair} ################################## @@ -352,9 +360,11 @@ finishedgoodsprefix: {enter_sp_config}{Down 10} enablefinishedgoods: {finishedgoodsprefix}e^ disablefinishedgoods: {finishedgoodsprefix}d^ -crafts: {finishedgoodsprefix}{Right}f{Right}{Down 9}{togglesequence 9}^ -goblets: {finishedgoodsprefix}{Right}f{Right}{Down 2}&^ -jugs: {finishedgoodsprefix}{Right}f{Right}{Up 2}&{Left}{Down 2}f{Down}f{Down}f^ +crafts: {finishedgoodsprefix}{Right}f{Right}{Down 9}{togglesequence 9}^ +goblets: {finishedgoodsprefix}{Right}f{Right}{Down 2}&^ +jugs: {finishedgoodsprefix}{Right}f{Right}{Up 2}&{Left}{Down 2}f{Down}f{Down}f^ +stonetools: {finishedgoodsprefix}{Right}f{Right}{Up 2}&{Left}{Down 2}f{Down}f{Down}f^ +woodentools: {finishedgoodsprefix}{Right}f{Right}{Up 2}&{Left}{Down}f{Down}f{Down}f{Down}f{Right}&^ forbidcrafts: {finishedgoodsprefix}{Right 2}{Down 9}{togglesequence 9}^ forbidgoblets: {finishedgoodsprefix}{Right 2}{Down 2}&^ @@ -385,6 +395,15 @@ adamantinethread: {clothprefix}b{Right}{Down 3}p^ cloth: {clothprefix}b{Right}{Down 4}p{Down}p{Down}p^ adamantinecloth: {clothprefix}b{Right}{Up}p^ +forbidthread: {clothprefix}{Right}f{Down}f{Down}f^ +forbidadamantinethread: {clothprefix}{Right}{Down 3}f^ +forbidcloth: {clothprefix}{Right}{Down 4}f{Down}f{Down}f^ +forbidadamantinecloth: {clothprefix}{Right}{Up}f^ + +permitthread: {clothprefix}{Right}p{Down}p{Down}p^ +permitadamantinethread: {clothprefix}{Right}{Down 3}p^ +permitcloth: {clothprefix}{Right}{Down 4}p{Down}p{Down}p^ +permitadamantinecloth: {clothprefix}{Right}{Up}p^ ################################## # weapon stockpile adjustments diff --git a/dfhack.init-example b/dfhack.init-example index d181fcaa7..bcc5615d6 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -36,6 +36,7 @@ keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave # gui/quickfort script - apply pre-made blueprints to the map keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort +keybinding add Alt-F@dwarfmode gui/quickfort # gui/rename script - rename units and buildings keybinding add Ctrl-Shift-N gui/rename diff --git a/docs/changelog.txt b/docs/changelog.txt index 4dd8404ac..19518fa75 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -43,7 +43,16 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - `confirm`: added a confirmation dialog for removing manager orders - `confirm`: allow players to pause the confirmation dialog until they exit the current screen -- `dfhack-examples-guide`: refine food preparation orders and fix conditions for making jugs and pots in the ``basic`` manager orders +- `dfhack-examples-guide`: refine food preparation orders so meal types are chosen intelligently according to the amount of meals that exist and the number of aviailable items to cook with +- `dfhack-examples-guide`: reduce required stock of dye for "Dye cloth" orders +- `dfhack-examples-guide`: fix material conditions for making jugs and pots +- `dfhack-examples-guide`: make wooden jugs by default to differentiate them from other stone tools. this allows players to more easily select jugs out with a properly-configured stockpile (i.e. the new ``woodentools`` alias) +- `quickfort-alias-guide`: new aliases: ``forbidsearch``, ``permitsearch``, and ``togglesearch`` use the `search-plugin` plugin to alter the settings for a filtered list of item types when configuring stockpiles +- `quickfort-alias-guide`: new aliases: ``stonetools`` and ``woodentools``. the ``jugs`` alias is deprecated. please use ``stonetools`` instead, which is the same as the old ``jugs`` alias. +- `quickfort-alias-guide`: new aliases: ``usablehair``, ``permitusablehair``, and ``forbidusablehair`` alter settings for the types of hair/wool that can be made into cloth: sheep, llama, alpaca, and troll. The ``craftrefuse`` aliases have been altered to use this alias as well. +- `quickfort-alias-guide`: new aliases: ``forbidthread``, ``permitthread``, ``forbidadamantinethread``, ``permitadamantinethread``, ``forbidcloth``, ``permitcloth``, ``forbidadamantinecloth``, and ``permitadamantinecloth`` give you more control how adamantine-derived items are stored +- `quickfort`: `Dreamfort ` blueprint set improvements: automatically create tavern, library, and temple locations (restricted to residents only by default), automatically associate the rented rooms with the tavern +- `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including a werebeast-proof hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. ## Documentation diff --git a/docs/guides/quickfort-alias-guide.rst b/docs/guides/quickfort-alias-guide.rst index 37de7603b..6afad7569 100644 --- a/docs/guides/quickfort-alias-guide.rst +++ b/docs/guides/quickfort-alias-guide.rst @@ -521,6 +521,9 @@ give10right give move togglesequence togglesequence2 +forbidsearch search +permitsearch search +togglesearch search masterworkonly prefix artifactonly prefix togglemasterwork prefix @@ -588,6 +591,12 @@ four adjacent items:: dye: {foodprefix}b{Right}{Down 11}{Right}{Down 28}{togglesequence 4}^ +``forbidsearch``, ``permitsearch``, and ``togglesearch`` use the DFHack +`search-plugin` plugin to forbid or permit a filtered list, or toggle the first +(or only) item in the list. Specify the search string in the ``search`` +sub-alias. Be sure to move the cursor over to the right column before invoking +these aliases. The search filter will be cleared before this alias completes. + Finally, the ``masterwork`` and ``artifact`` group of aliases configure the corresponding allowable core quality for the stockpile categories that have them. This alias is used to implement category-specific aliases below, like @@ -724,13 +733,16 @@ shells forbidshells permitshells teeth forbidteeth permitteeth horns forbidhorns permithorns hair forbidhair permithair +usablehair forbidusablehair permitusablehair craftrefuse forbidcraftrefuse permitcraftrefuse =========== ================== ================== Notes: -* ``craftrefuse`` includes everything a craftsdwarf can use: skulls, bones, - shells, teeth, horns, and hair. +* ``usablehair`` Only hair and wool that can make usable clothing is included, + i.e. from sheep, llamas, alpacas, and trolls. +* ``craftrefuse`` includes everything a craftsdwarf or tailor can use: skulls, + bones, shells, teeth, horns, and "usable" hair/wool (defined above). Stone stockpile adjustments ``````````````````````````` @@ -802,7 +814,8 @@ Finished goods stockpile adjustments ======================= ============================= ============================= Exclusive Forbid Permit ======================= ============================= ============================= -jugs +stonetools +woodentools crafts forbidcrafts permitcrafts goblets forbidgoblets permitgoblets masterworkfinishedgoods forbidmasterworkfinishedgoods permitmasterworkfinishedgoods @@ -812,17 +825,18 @@ artifactfinishedgoods forbidartifactfinishedgoods permitartifactfinishedgo Cloth stockpile adjustments ``````````````````````````` -+------------------+ -| Exclusive | -+==================+ -| thread | -+------------------+ -| adamantinethread | -+------------------+ -| cloth | -+------------------+ -| adamantinecloth | -+------------------+ +================ ====================== ====================== +Exclusive Forbid Permit +================ ====================== ====================== +thread forbidthread permitthread +adamantinethread forbidadamantinethread permitadamantinethread +cloth forbidcloth permitcloth +adamantinecloth forbidadamantinecloth permitadamantinecloth +================ ====================== ====================== + +Notes: + +* ``thread`` and ``cloth`` refers to all materials that are not adamantine. Weapon stockpile adjustments ```````````````````````````` diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 5225aea34..a886773f9 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -1418,15 +1418,10 @@ Tips and tricks Caveats and limitations ----------------------- -- If you use the ``jugs`` alias in your ``#query``-mode blueprints, be aware - that there is no way to differentiate jugs from other types of tools in the - game. Therefore, ``jugs`` stockpiles will also take nest boxes, scroll - rollers, and other tools. The only workaround is not to have other tools - lying around in your fort. - -- Likewise for the ``bags`` alias. The game does not differentiate between - empty and full bags, so you'll get bags of gypsum power in your "bags" - stockpile unless you are careful to assign all your gypsum to your hospital. +- If you use the the ``bags`` alias, be aware that the game does not + differentiate between empty and full bags. Therefore, you can get bags of + gypsum power in your "bags" stockpile unless you are careful to assign all + your gypsum to your hospital. - Weapon traps and upright spear/spike traps can currently only be built with a single weapon. From c277d7cedb83977f22ce1c6d3aafda449db044b2 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 3 Jun 2022 20:03:29 +0000 Subject: [PATCH 085/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index b70dec06c..1ff42c6ef 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b70dec06ca2a8622bce6cdece7153852356591f2 +Subproject commit 1ff42c6ef47933f4cb71fcd333f9ff520678e11f From 514e5ee5bb2e228e4d4564042aab037473e487ca Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 3 Jun 2022 13:18:16 -0700 Subject: [PATCH 086/854] convert status message to debug message ref: #2164 --- plugins/confirm.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index 60735ef94..bf102d2ba 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -5,6 +5,7 @@ #include "Console.h" #include "Core.h" #include "DataDefs.h" +#include "Debug.h" #include "Error.h" #include "Export.h" #include "LuaTools.h" @@ -50,6 +51,9 @@ bool paused = false; // if set, confirm will unpause when this screen is no longer on the stack df::viewscreen *paused_screen = NULL; +namespace DFHack { + DBG_DECLARE(confirm,status); +} template inline bool in_vector (std::vector &vec, FT item) @@ -238,7 +242,7 @@ namespace conf_lua { } int unpause(lua_State *) { - Core::getInstance().print("unpausing\n"); + DEBUG(status).print("unpausing\n"), paused = false; paused_screen = NULL; return 0; @@ -333,7 +337,7 @@ public: set_state(SELECTED); else if (input->count(df::interface_key::CUSTOM_P)) { - Core::getInstance().print("pausing\n"); + DEBUG(status).print("pausing\n"), paused = true; // only record the screen when we're not at the top viewscreen // since this screen will *always* be on the stack. for From d9addb0f24a10c9797307f39d14c726d7ba722e5 Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 3 Jun 2022 13:19:56 -0700 Subject: [PATCH 087/854] [gui/quantum] add integration test (#2175) * add integration test for gui/quantum * exercise the gui/quantum ui a little more --- .../ecosystem/golden/gui_quantum-build.csv | 5 + .../ecosystem/golden/gui_quantum-place.csv | 6 + .../test/ecosystem/golden/meta-dig.csv | 2 +- .../test/ecosystem/golden/transform-build.csv | 2 +- .../test/ecosystem/golden/transform-dig.csv | 2 +- .../test/ecosystem/in/gui_quantum-build.csv | 1 + .../test/ecosystem/in/gui_quantum-place.csv | 2 + .../test/ecosystem/in/gui_quantum-spec.csv | 5 + test/quickfort/ecosystem.lua | 164 +++++++++++++++--- 9 files changed, 165 insertions(+), 24 deletions(-) create mode 100644 data/blueprints/library/test/ecosystem/golden/gui_quantum-build.csv create mode 100644 data/blueprints/library/test/ecosystem/golden/gui_quantum-place.csv create mode 100644 data/blueprints/library/test/ecosystem/in/gui_quantum-build.csv create mode 100644 data/blueprints/library/test/ecosystem/in/gui_quantum-place.csv create mode 100644 data/blueprints/library/test/ecosystem/in/gui_quantum-spec.csv diff --git a/data/blueprints/library/test/ecosystem/golden/gui_quantum-build.csv b/data/blueprints/library/test/ecosystem/golden/gui_quantum-build.csv new file mode 100644 index 000000000..102c98f4a --- /dev/null +++ b/data/blueprints/library/test/ecosystem/golden/gui_quantum-build.csv @@ -0,0 +1,5 @@ +#build label(build) + + + +,,CSdd diff --git a/data/blueprints/library/test/ecosystem/golden/gui_quantum-place.csv b/data/blueprints/library/test/ecosystem/golden/gui_quantum-place.csv new file mode 100644 index 000000000..0cb7a5b6b --- /dev/null +++ b/data/blueprints/library/test/ecosystem/golden/gui_quantum-place.csv @@ -0,0 +1,6 @@ +#place label(place) +s(5x3) + + + +,,afunswebhlzSgpd diff --git a/data/blueprints/library/test/ecosystem/golden/meta-dig.csv b/data/blueprints/library/test/ecosystem/golden/meta-dig.csv index 827ccaf73..456d2ba92 100644 --- a/data/blueprints/library/test/ecosystem/golden/meta-dig.csv +++ b/data/blueprints/library/test/ecosystem/golden/meta-dig.csv @@ -1,4 +1,4 @@ -#dig label(dig) +#dig label(dig) start(3;3) d,d,,,d d,,j,,d d,u,d,u,d diff --git a/data/blueprints/library/test/ecosystem/golden/transform-build.csv b/data/blueprints/library/test/ecosystem/golden/transform-build.csv index feb7a3cb5..2cab6e804 100644 --- a/data/blueprints/library/test/ecosystem/golden/transform-build.csv +++ b/data/blueprints/library/test/ecosystem/golden/transform-build.csv @@ -1,4 +1,4 @@ -#build label(build) +#build label(build) start(14;14) ,trackNS,trackE,,trackW,trackS,trackN,,gs(1x2),ga(2x1),,gx(1x2),gw(1x2),,gw(1x2),gx(1x2),gd(2x1),,gs(1x2),,trackN,trackS,trackE,,trackW,trackNS trackEW,,trackSE,,trackSW,trackNE,trackNW,,,gd(2x1),,,,,,,ga(2x1),,,,trackNE,trackNW,trackSE,,trackSW,,trackEW trackS,trackSE,,,trackNSE,trackNSW,trackEW,,Mrsssqq(2x1),,,Msh,,,,,Msk,Mrsqq(2x1),,,trackEW,trackNSE,trackNSW,,,trackSW,trackS diff --git a/data/blueprints/library/test/ecosystem/golden/transform-dig.csv b/data/blueprints/library/test/ecosystem/golden/transform-dig.csv index 3772f2d5f..b26339d8a 100644 --- a/data/blueprints/library/test/ecosystem/golden/transform-dig.csv +++ b/data/blueprints/library/test/ecosystem/golden/transform-dig.csv @@ -1,4 +1,4 @@ -#dig label(dig) +#dig label(dig) start(14;14) ,d,d,,d,d,d,,d,d,d,d,d,,d,d,d,d,d,,d,d,d,,d,d d,,d,,d,d,d,,d,d,d,d,d,,d,d,d,d,d,,d,d,d,,d,,d d,d,,,d,d,d,,d,d,d,d,d,,d,d,d,d,d,,d,d,d,,,d,d diff --git a/data/blueprints/library/test/ecosystem/in/gui_quantum-build.csv b/data/blueprints/library/test/ecosystem/in/gui_quantum-build.csv new file mode 100644 index 000000000..ebbc0207d --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/gui_quantum-build.csv @@ -0,0 +1 @@ +#build diff --git a/data/blueprints/library/test/ecosystem/in/gui_quantum-place.csv b/data/blueprints/library/test/ecosystem/in/gui_quantum-place.csv new file mode 100644 index 000000000..ec9f6e42d --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/gui_quantum-place.csv @@ -0,0 +1,2 @@ +#place label(place) +s(5x3) diff --git a/data/blueprints/library/test/ecosystem/in/gui_quantum-spec.csv b/data/blueprints/library/test/ecosystem/in/gui_quantum-spec.csv new file mode 100644 index 000000000..91fde500d --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/gui_quantum-spec.csv @@ -0,0 +1,5 @@ +#notes +description=integration test for the gui/quantum script +width=5 +height=5 +extra_fn=test_gui_quantum diff --git a/test/quickfort/ecosystem.lua b/test/quickfort/ecosystem.lua index 320dc9979..39d0f6be1 100644 --- a/test/quickfort/ecosystem.lua +++ b/test/quickfort/ecosystem.lua @@ -10,9 +10,11 @@ -- height (required) -- depth (default is 1) -- start (cursor offset for input blueprints, default is 1,1) +-- extra_fn (the name of a global function in this file to run after applying +-- all blueprints but before comparing results) -- -- depends on blueprint, buildingplan, and dig-now plugins (as well as the --- quickfort script, of course) +-- quickfort script and anything else run in the extra_fns, of course) -- -- note that this test harness cannot (yet) test #query blueprints that define -- rooms since furniture is not actually built during the test. It also cannot @@ -24,10 +26,18 @@ config.mode = 'fortress' local argparse = require('argparse') +local gui = require('gui') +local guidm = require('gui.dwarfmode') +local utils = require('utils') + local blueprint = require('plugins.blueprint') +local confirm = require('plugins.confirm') + +local assign_minecarts = reqscript('assign-minecarts') +local quantum = reqscript('gui/quantum') +local quickfort = reqscript('quickfort') local quickfort_list = reqscript('internal/quickfort/list') local quickfort_command = reqscript('internal/quickfort/command') -local utils = require('utils') local blueprints_dir = 'blueprints/' local input_dir = 'library/test/ecosystem/in/' @@ -86,14 +96,14 @@ local function get_blueprint_sets() local _,_,file_part = fname:find('/([^/]+)$') local _,_,basename = file_part:find('^([^-.]+)') if not sets[basename] then sets[basename] = {spec={}, phases={}} end - local golden_path = blueprints_dir..golden_dir..file_part - if not os_exists(golden_path) then - golden_path = blueprints_dir..fname + local golden_path = golden_dir..file_part + if not os_exists(blueprints_dir..golden_path) then + golden_path = fname end sets[basename].phases[phase] = { listnum=listnum, golden_filepath=golden_path, - output_filepath=blueprints_dir..output_dir..file_part} + output_filepath=output_dir..file_part} end end @@ -193,17 +203,17 @@ local function get_cursor_arg(pos, start) return ('--cursor=%d,%d,%d'):format(pos.x+start.x-1, pos.y+start.y-1, pos.z) end -local function quickfort_cmd(cmd, listnum, pos, start) - dfhack.run_script('quickfort', cmd, '-q', listnum, +local function quickfort_cmd(cmd, listnum_or_path, pos, start) + dfhack.run_script('quickfort', cmd, '-q', listnum_or_path, get_cursor_arg(pos, start)) end -local function quickfort_run(listnum, pos, start) - quickfort_cmd('run', listnum, pos, start) +local function quickfort_run(listnum_or_path, pos, start) + quickfort_cmd('run', listnum_or_path, pos, start) end -local function quickfort_undo(listnum, pos, start) - quickfort_cmd('undo', listnum, pos, start) +local function quickfort_undo(listnum_or_path, pos, start) + quickfort_cmd('undo', listnum_or_path, pos, start) end local function designate_area(pos, spec) @@ -224,10 +234,20 @@ local function run_dig_now(area) format_pos(area.endpos), '--clean') end -local function run_blueprint(basename, set, pos) - blueprint.run(tostring(set.spec.width), tostring(set.spec.height), - tostring(-set.spec.depth), output_dir..basename, - get_cursor_arg(pos), '-tphase') +local function get_playback_start_arg(start) + if not start then return end + return ('--playback-start=%d,%d'):format(start.x, start.y) +end + +local function run_blueprint(basename, spec, pos) + local args = {tostring(spec.width), tostring(spec.height), + tostring(-spec.depth), output_dir..basename, + get_cursor_arg(pos), '-tphase'} + local playback_start_arg = get_playback_start_arg(spec.start) + if playback_start_arg then + table.insert(args, playback_start_arg) + end + blueprint.run(table.unpack(args)) end local function reset_area(area, spec) @@ -308,13 +328,19 @@ function test.end_to_end() if phases.zone then do_phase(phases.zone, area, spec) end if phases.query then do_phase(phases.query, area, spec) end + -- run any extra commands, if defined by the blueprint spec + if spec.extra_fn then + _ENV[spec.extra_fn](area.pos) + end + -- run blueprint to generate files in output dir - run_blueprint(basename, set, area.pos) + run_blueprint(basename, spec, area.pos) - -- quickfort undo blueprints + -- quickfort undo blueprints (order shouldn't matter) for _,phase_name in ipairs(phase_names) do if phases[phase_name] then - quickfort_undo(phases[phase_name].listnum, area.pos, spec.start) + quickfort_undo(phases[phase_name].golden_filepath, + area.pos, spec.start) end end @@ -326,8 +352,8 @@ function test.end_to_end() for phase,phase_data in pairs(phases) do if phase == 'notes' then goto continue end print((' verifying phase: %s'):format(phase)) - local golden_filepath = phase_data.golden_filepath - local output_filepath = phase_data.output_filepath + local golden_filepath = blueprints_dir..phase_data.golden_filepath + local output_filepath = blueprints_dir..phase_data.output_filepath local input_hash, input_size = md5File(golden_filepath) local output_hash, output_size = md5File(output_filepath) expect.eq(input_hash, output_hash, @@ -345,6 +371,7 @@ function test.end_to_end() input:close() output:close() expect.table_eq(input_lines, output_lines) + return nil end ::continue:: end @@ -352,3 +379,98 @@ function test.end_to_end() ::continue:: end end + +local function send_keys(...) + local keys = {...} + for _,key in ipairs(keys) do + gui.simulateInput(dfhack.gui.getCurViewscreen(true), key) + end +end + +function test_gui_quantum(pos) + local vehicles = assign_minecarts.get_free_vehicles() + local confirm_state = confirm.isEnabled() + local confirm_conf = confirm.get_conf_data() + local routes = df.global.ui.hauling.routes + local num_routes = #routes + local next_order_id = df.global.world.manager_order_next_id + + return dfhack.with_finalize( + function() + -- unforbid the minecarts we forbade + for _,minecart in ipairs(vehicles) do + local item = df.item.find(minecart.item_id) + if not item then error('could not find item in list') end + item.flags.forbid = false + end + + if confirm_state then + dfhack.run_command('enable confirm') + for _,c in pairs(confirm_conf) do + confirm.set_conf_state(c.id, c.enabled) + end + end + end, + function() + -- forbid all available minecarts + for _,minecart in ipairs(vehicles) do + local item = df.item.find(minecart.item_id) + if not item then error('could not find item in list') end + item.flags.forbid = true + end + + dfhack.run_script('gui/quantum') + local view = quantum.view + view:onRender() + guidm.setCursorPos(pos) + -- select the feeder stockpile + send_keys('CURSOR_RIGHT', 'CURSOR_RIGHT', 'SELECT') + view:onRender() + -- deselect the feeder stockpile + send_keys('LEAVESCREEN') + view:onRender() + -- reselect the feeder stockpile + send_keys('SELECT') + view:onRender() + -- set a custom name + send_keys('CUSTOM_N') + view:onRender() + view:onInput({_STRING=string.byte('f')}) + view:onInput({_STRING=string.byte('o')}) + view:onInput({_STRING=string.byte('o')}) + send_keys('SELECT') + -- rotate the dump direction to the south + send_keys('CUSTOM_D') + view:onRender() + -- move the cursor to the dump position + send_keys('CURSOR_DOWN', 'CURSOR_DOWN', 'CURSOR_DOWN') + view:onRender() + -- commit and dismiss the dialog + send_keys('SELECT', 'SELECT') + + -- verify the created route + expect.eq(num_routes + 1, #routes) + local route = routes[#routes-1] + expect.eq(0, #route.vehicle_ids, 'minecart should not be assigned') + expect.eq(1, #route.stops, 'should have 1 stop') + expect.eq(1, #route.stops[0].stockpiles, + 'should have 1 link') + + -- verify the created order + expect.eq(next_order_id + 1, df.global.world.manager_order_next_id) + local orders = df.global.world.manager_orders + local order = orders[#orders - 1] + expect.eq(df.job_type.MakeTool, order.job_type) + + -- if confirm is enabled, temporarily disable it so we can remove + -- the route and manager order via the ui (easier than walking the + -- structures and carefully deleting all the memory) + if confirm_state then + dfhack.run_command('disable confirm') + end + -- delete last route + quickfort.apply_blueprint{mode='config', data='h--x^'} + -- delete last manager order + quickfort.apply_blueprint{mode='config', data='jm{Up}r^^'} + end) +end From a8916a269e14137093837a68f70f159e1a977533 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 3 Jun 2022 13:36:09 -0700 Subject: [PATCH 088/854] fix syntax error ref: #2164 --- plugins/confirm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index bf102d2ba..32da4e1f8 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -242,7 +242,7 @@ namespace conf_lua { } int unpause(lua_State *) { - DEBUG(status).print("unpausing\n"), + DEBUG(status).print("unpausing\n"); paused = false; paused_screen = NULL; return 0; @@ -337,7 +337,7 @@ public: set_state(SELECTED); else if (input->count(df::interface_key::CUSTOM_P)) { - DEBUG(status).print("pausing\n"), + DEBUG(status).print("pausing\n"); paused = true; // only record the screen when we're not at the top viewscreen // since this screen will *always* be on the stack. for From f290b1c804f6207afc786b3a3ca83e8cd87c44d9 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 4 Jun 2022 07:16:44 +0000 Subject: [PATCH 089/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index f503096c1..b5ec10a98 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f503096c130661dda7a9b7e4b471b0854981f0f5 +Subproject commit b5ec10a9817d0990f8c39cceb0c3a2cb69fca10c diff --git a/scripts b/scripts index 1ff42c6ef..a1f31a8d9 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 1ff42c6ef47933f4cb71fcd333f9ff520678e11f +Subproject commit a1f31a8d99db600b225f1022c490bfa63ade935a From 42c35b05cc8e30bff3bbf5654af3165bcc921ab8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 3 Jun 2022 17:02:01 -0700 Subject: [PATCH 090/854] update help text for library blueprints refer users to gui/blueprint for interactive positioning --- data/blueprints/library/aquifer_tap.csv | 13 +++++++------ data/blueprints/library/pump_stack.csv | 22 +++++++++++----------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/data/blueprints/library/aquifer_tap.csv b/data/blueprints/library/aquifer_tap.csv index 22025d8e7..649e9161e 100644 --- a/data/blueprints/library/aquifer_tap.csv +++ b/data/blueprints/library/aquifer_tap.csv @@ -5,21 +5,21 @@ Here's the procedure: "" 1) Dig a tunnel from where you want the water to end up (e.g. your well cistern) off to an unused portion of the map. Be sure to dig a one-tile-wide diagonal segment in this tunnel so water that will eventually flow through the tunnel is depressurized. "" -"2) From the end of that tunnel, dig a staircase straight up to the z-level just below the lowest aquifer level. Also dig the staircase down one z-level." +"2) From the end of that tunnel, dig a staircase straight up to the z-level just below the lowest aquifer level. Also dig the staircase down one z-level below the tunnel level." "" -"3) From the bottom of the staircase (the z-level below where the water will flow to your cisterns), dig a straight, one-tile wide tunnel to the edge of the map. Smooth the map edge tile and carve a fortification. The water can flow through the fortification and off the map, allowing the dwarves to dig out the aquifer tap without drowning." +"3) From the bottom of the staircase (the z-level below where the water will flow to your cisterns), dig a straight, one-tile wide tunnel to the edge of the map. This is your drainage tunnel. Smooth the map edge tile and carve a fortification. The water can flow through the fortification and off the map, allowing the dwarves to dig out the aquifer tap without drowning." "" -4) Place a lever-controlled floodgate in the drainage tunnel and open the floodgate. +4) Place a lever-controlled floodgate in the drainage tunnel and open the floodgate. Place the lever somewhere else in your fort so that it will remain dry and accessible. "" -"5) If you care about how nice things look, haul away any boulders and smooth the tiles. You won't be able to access any of this area once it fills up with water!" +"5) If you care about how nice things look, haul away any boulders in the tunnels and smooth the tiles. You won't be able to access any of this area once it fills up with water!" "" -"6) Apply this blueprint (quickfort run library/aquifer_tap.csv -n /dig) to the z-level above the top of the staircase, inside the lowest aquifer level. Do not unpause until after the next step." +"6) Apply this blueprint (gui/quickfort library/aquifer_tap.csv -n /dig) to the z-level above the top of the staircase, inside the lowest aquifer level. Do not unpause until after the next step." "" "7) Damp tiles cancel dig designations if the tile is currently hidden, so to avoid having to re-apply this blueprint after every tile your dwarves dig, position the cursor straight up (north) of the left-most tile designated for digging and straight left (west) of the topmost designated tile and run the following commands in the DFHack console:" tiletypes-command f any ; f designated 1 ; p any ; p hidden 0 ; r 23 23 1 tiletypes-here "" -"8) Unpause and dig out the tap. If you care about appearances, haul away any boulders and feel free to remove the ramps (d-z). The water will safely flow down the staircase, through the open floodgate, and off the map until you choose to close the floodgate." +"8) Unpause and dig out the tap. If you care about appearances, haul away any boulders and feel free to remove the ramps (d-z). The water will safely flow down the staircase, through the open floodgate, down the drainage tunnel, and off the map until you choose to close the floodgate." "9) Once everything is dug out and all dwarves are out of the waterways, close the floodgate. Your cisterns will fill with water. Since the waterway to your cisterns is depressurized, the cisterns will stay forever full, but will not flood." "" @@ -58,3 +58,4 @@ u <- drainage level ,,,,,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r ,,,,,,,,,,,,r ,,,,,,,,,,,,r + diff --git a/data/blueprints/library/pump_stack.csv b/data/blueprints/library/pump_stack.csv index c49a2c310..6bd129308 100644 --- a/data/blueprints/library/pump_stack.csv +++ b/data/blueprints/library/pump_stack.csv @@ -1,23 +1,23 @@ #notes label(help) A pump stack is useful for moving water or magma up through the z-levels. "" -To use these blueprints: +"These blueprints can be used from the quickfort commandline, but are much easier to use with the visual interface. That way you can check the vertical path interactively before you apply. Run gui/quickfort pump_stack" "" -1) Measure how many z-levels the pump stack should span. +"1) Select the ""dig"" blueprint and position the blueprint preview on the bottom level of the future pump stack. It should be on the z-level just above the liquid you want to pump." "" -"2) Position the cursor on the bottom level of the future pump stack. It should be on the z-level just above the liquid you want to pump. Run ""quickfort run library/pump_stack.csv -n /dig2SN"" to see where the suction hole will end up. Replace ""run"" with ""undo"" in the previous command to clean up." +"2) Enable repetitions with the ""R"" hotkey and lock the blueprint in place with the ""L"" hotkey. Move up the z-levels to check that the pump stack has a clear path and doesn't intersect with any open areas (e.g. caverns). Increase the number of repetitions with the ""+"" or ""*"" hotkeys if you need the pump stack to extend further up. Unlock the blueprint and shift it around if you need to, then lock it again to recheck the vertical path." "" -"3) If you need an East-West pump stack, or if you need the staircase in another spot, use the ""--transform"" commandline option to alter the blueprint to your needs. For example: ""quickfort run library/pump_stack.csv -n /dig2SN --transform rotcw,fliph"". If you use a transformation, be sure to use the same option for the remaining commandlines." +"3) If you need to flip the pump stack around to make it fit through the rock layers, enable transformations with the ""t"" hotkey and rotate/flip the blueprint with Ctrl+arrow keys." "" -"4) Once you have everything lined up, run ""quickfort run library/pump_stack.csv -n /dig2SN --repeat up,20"" to designate the entire pump stack for digging. Replace that last ""20"" with the height of your pump stack divided by 2 (since each repetition of /dig2SN is two z-levels high). If the height ends up being one too many at the top, manually undesignate the top level." +"4) Once you have everything lined up, hit Enter to apply. If the height ends up being one too many at the top, manually undesignate the top level." "" -"5) Since you do not need to transmit power down below the lowest level, replace the channel designation on the middle tile of the bottom-most pump stack level with a regular dig designation. Likewise, replace the Up/Down staircase designation on the lowest level with an Up staircase designation." +"5) Since you do not need to transmit power down below the lowest level, replace the channel designation on the middle tile of the bottom-most pump stack level with a regular dig designation. Likewise, replace the Up/Down staircase designation on the lowest level with an Up staircase designation. Otherwise you might get magma critters climbing up through your access stairway!" "" -"6) After the stack is dug out, prepare for building by setting the buildingplan plugin material filters for screw pumps (b-M-s-M). If you are planning to move magma, be sure to select magma-safe materials." +"6) After the stack is dug out, prepare for building by setting the buildingplan plugin material filters for screw pumps (b-M-s-M). If you are planning to move magma, be sure to select magma-safe materials (like green glass) for all three components of the screw pump." "" -"7) Finally, position the cursor back on the access stairs on the lowest level and run ""quickfort run library/pump_stack.csv -n /build2SN --repeat up,20"" (with 20 replaced with your desired repetition count and with your --transform command, if any)." +"7) Finally, position the cursor back on the access stairs on the lowest level and run the ""build"" blueprint with the same repetition and transformation settings that you used for the ""dig"" blueprint. As you manufacture the materials you need to construct the screw pumps, your dwarves will build the pump stack from the bottom up." "" -"Sometimes, a screw pump will spontaneously deconstruct while you are building the stack. This will reduce the efficiency of the stack a little, but it's nothing to worry about. Just re-run the /build2SN blueprint over the entire stack to ""fix up"" any broken pieces. The blueprint will harmlessly skip over any correctly-built screw pumps." +"Sometimes, a screw pump will spontaneously deconstruct while you are building the stack. This will reduce the efficiency of the stack a little, but it's nothing to worry about. Just re-run the ""build"" blueprint over the entire stack to ""fix up"" any broken pieces. The blueprint will harmlessly skip over any correctly-built screw pumps." "" See the wiki for more info on pump stacks: https://dwarffortresswiki.org/index.php/Screw_pump#Pump_stack #dig label(digSN) start(2;4;on access stairs) hidden() for a pump from south level @@ -34,7 +34,7 @@ See the wiki for more info on pump stacks: https://dwarffortresswiki.org/index.p ,i,,h ,,,d -#meta label(dig2SN) start(at the bottom level on the access stairs) 2 levels of pump stack - bottom level pumps from the south +#meta label(dig) start(at the bottom level on the access stairs) 2 levels of pump stack - bottom level pumps from the south /digSN #< /digNS @@ -52,7 +52,7 @@ See the wiki for more info on pump stacks: https://dwarffortresswiki.org/index.p ,`,,Msu ,,,` -#meta label(build2SN) start(at the bottom level on the access stairs) 2 levels of pump stack - bottom level pumps from the south +#meta label(build) start(at the bottom level on the access stairs) 2 levels of pump stack - bottom level pumps from the south /buildSN #< /buildNS From 3928bb67d7fbff07703286f8d4831b6631ff3947 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 3 Jun 2022 17:15:57 -0700 Subject: [PATCH 091/854] promote gui/quickfort in the library guide also add a link to the TheQuickFortress online spreadsheets --- docs/guides/quickfort-library-guide.rst | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/docs/guides/quickfort-library-guide.rst b/docs/guides/quickfort-library-guide.rst index 366272052..2f9b3a06f 100644 --- a/docs/guides/quickfort-library-guide.rst +++ b/docs/guides/quickfort-library-guide.rst @@ -7,10 +7,12 @@ Blueprint Library Index This guide contains a high-level overview of the blueprints available in the :source:`quickfort blueprint library `. You can list library blueprints by running ``quickfort list --library`` or by hitting -:kbd:`Alt`:kbd:`l` in the ``quickfort gui`` interactive dialog. +:kbd:`Alt`:kbd:`l` in the ``gui/quickfort`` file load dialog. Each file is hyperlinked to its online version so you can see exactly what the -blueprints do before you run them. +blueprints do before you run them. Also, if you use `gui/quickfort`, you will +get a live preview of which tiles will be modified by the blueprint before you +apply it to your map. Whole fort blueprint sets ------------------------- @@ -34,7 +36,7 @@ automate basic fort needs, such as food, booze, and item production. It can function by itself or as the core of a larger, more ambitious fortress. Read the high-level walkthrough by running ``quickfort run library/dreamfort.csv`` and list the walkthroughs for the individual levels by running ``quickfort list -l -dreamfort -m notes`` or ``quickfort gui -l dreamfort notes``. +dreamfort -m notes`` or ``gui/quickfort dreamfort notes``. Dreamfort blueprints are available for easy viewing and copying `online `__. @@ -47,14 +49,14 @@ and a convenient `checklist `__ from which you can copy the ``quickfort`` commands. -You can download a fully built Dreamfort-based fort from `dffd +If you like, you can download a fully built Dreamfort-based fort from `dffd `__, load it, and explore it interactively. Visual overview ``````````````` -Here are some annotated screenshots of the major levels (or click `here +Here are annotated screenshots of the major Dreamfort levels (or click `here `__ for a slideshow). @@ -136,7 +138,8 @@ The Quick Fortress is an updated version of the example fortress that came with inspired DFHack quickfort). While it is not a complete fortress by itself, it is much simpler than Dreamfort and is good for a first introduction to `quickfort` blueprints. Read its walkthrough with ``quickfort run -library/quickfortress.csv``. +library/quickfortress.csv`` or view the blueprints `online +`__. Layout helpers -------------- @@ -205,8 +208,8 @@ running ``quickfort run library/aquifer_tap.csv -n /help``. You can see how to nullify the water pressure (so you don't flood your fort) in the `Dreamfort screenshot above `. -Blueprint spreadsheet also available -`online `__ +The blueprint spreadsheet is also available +`online `__. Post-embark ~~~~~~~~~~~ @@ -219,9 +222,9 @@ starting stockpiles. Pump Stack ~~~~~~~~~~ -The pump stack blueprints help you move water and magma up to move convenient +The pump stack blueprints help you move water and magma up to more convenient locations in your fort. See the step-by-step guide for using it by running ``quickfort run library/pump_stack.csv -n /help``. -Blueprint spreadsheet also available -`online `__ +The blueprint spreadsheet is also available +`online `__. From 1424e8c867f8b7d3be31c339e113ae33c173f928 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 3 Jun 2022 17:19:11 -0700 Subject: [PATCH 092/854] remove extra EOF newline --- data/blueprints/library/aquifer_tap.csv | 1 - 1 file changed, 1 deletion(-) diff --git a/data/blueprints/library/aquifer_tap.csv b/data/blueprints/library/aquifer_tap.csv index 649e9161e..3cc560ce8 100644 --- a/data/blueprints/library/aquifer_tap.csv +++ b/data/blueprints/library/aquifer_tap.csv @@ -58,4 +58,3 @@ u <- drainage level ,,,,,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r ,,,,,,,,,,,,r ,,,,,,,,,,,,r - From 7b2cb8f9a631b8b44bfc2ff1f4e684d34b18b5f6 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sun, 5 Jun 2022 07:16:27 +0000 Subject: [PATCH 093/854] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index b5ec10a98..a24581cc5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b5ec10a9817d0990f8c39cceb0c3a2cb69fca10c +Subproject commit a24581cc5318bdbc6227f368f67bc03a9082c19c From 46f80bed3daeb105e6350a6ef12854447c245663 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 5 Jun 2022 21:46:24 -0700 Subject: [PATCH 094/854] allow ccache to function again --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 07febaa63..15c653bf8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,7 +65,9 @@ jobs: uses: actions/cache@v2 with: path: ~/.ccache - key: ccache-${{ matrix.os }}-gcc-${{ matrix.gcc }} + key: ccache-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.sha }} + restore-keys: | + ccache-${{ matrix.os }}-gcc-${{ matrix.gcc }} - name: Download DF run: | sh ci/download-df.sh From 5e0a947d518be1494f9b9f7135e668454812a261 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 6 Jun 2022 01:49:00 -0400 Subject: [PATCH 095/854] Fix changelog entry and release for #2028 --- docs/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 19518fa75..0e3c90855 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -111,6 +111,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - add functions reverse-engineered from ambushing unit code: ``Units::isHidden()``, ``Units::isFortControlled()``, ``Units::getOuterContainerRef()``, ``Items::getOuterContainerRef()`` +- ``Job::removeJob()``: use the job cancel vmethod graciously provided by The Toady One in place of a synthetic method derived from reverse engineering ## Lua - `custom-raw-tokens`: library for accessing tokens added to raws by mods @@ -161,7 +162,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - ``Buildings::findCivzonesAt()``: lookups now complete in constant time instead of linearly scanning through all civzones in the game -- ``Job::remove_postings()``: use the job cancel vmethod graciously provided by The Toady One in place of a synthetic method derived from reverse engineering ## Lua - ``argparse.processArgsGetopt()``: you can now have long form parameters that are not an alias for a short form parameter. For example, you can now have a parameter like ``--longparam`` without needing to have an equivalent one-letter ``-l`` param. From 97775766efe353c7e0a202af4644a07bc04cc445 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 6 Jun 2022 07:16:06 +0000 Subject: [PATCH 096/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index a1f31a8d9..52e21c5e0 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a1f31a8d99db600b225f1022c490bfa63ade935a +Subproject commit 52e21c5e0c78e17acdad23f94efffb043290d5b5 From 1f6726bb031da11c54b79edf18c018b76e5e99f7 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 5 Jun 2022 21:54:53 -0700 Subject: [PATCH 097/854] namespace the ccache key with the repo and branch --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15c653bf8..6dbb43e15 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,8 +65,9 @@ jobs: uses: actions/cache@v2 with: path: ~/.ccache - key: ccache-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.sha }} + key: ccache-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.repository_owner }}-${{ github.ref_name }}-${{ github.sha }} restore-keys: | + ccache-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.repository_owner }}-${{ github.ref_name }} ccache-${{ matrix.os }}-gcc-${{ matrix.gcc }} - name: Download DF run: | From 9c31e4b42384fc6cb1f1fd5de0ba73fd77cf1127 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 5 Jun 2022 22:02:25 -0700 Subject: [PATCH 098/854] change the key so we no longer match our old cache --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6dbb43e15..8ab2221c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,10 +65,10 @@ jobs: uses: actions/cache@v2 with: path: ~/.ccache - key: ccache-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.repository_owner }}-${{ github.ref_name }}-${{ github.sha }} + key: ccache-v2-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.repository_owner }}-${{ github.ref_name }}-${{ github.sha }} restore-keys: | - ccache-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.repository_owner }}-${{ github.ref_name }} - ccache-${{ matrix.os }}-gcc-${{ matrix.gcc }} + ccache-v2-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.repository_owner }}-${{ github.ref_name }} + ccache-v2-${{ matrix.os }}-gcc-${{ matrix.gcc }} - name: Download DF run: | sh ci/download-df.sh From 24d2e8e115b0c4721b34191d04863ea216e5f2b8 Mon Sep 17 00:00:00 2001 From: Myk Date: Sun, 5 Jun 2022 23:41:11 -0700 Subject: [PATCH 099/854] Update build.yml --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ab2221c7..4ef4187be 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,9 +65,9 @@ jobs: uses: actions/cache@v2 with: path: ~/.ccache - key: ccache-v2-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.repository_owner }}-${{ github.ref_name }}-${{ github.sha }} + key: ccache-v2-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.ref_name }}-${{ github.sha }} restore-keys: | - ccache-v2-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.repository_owner }}-${{ github.ref_name }} + ccache-v2-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.ref_name }} ccache-v2-${{ matrix.os }}-gcc-${{ matrix.gcc }} - name: Download DF run: | From 1a4a2ec63a1a967d2726dd7b13443d4f7c9b7d1a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jun 2022 19:34:53 +0000 Subject: [PATCH 100/854] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/python-jsonschema/check-jsonschema: 0.14.3 → 0.16.0](https://github.com/python-jsonschema/check-jsonschema/compare/0.14.3...0.16.0) - [github.com/Lucas-C/pre-commit-hooks: v1.1.13 → v1.2.0](https://github.com/Lucas-C/pre-commit-hooks/compare/v1.1.13...v1.2.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 336bda270..d1bb4ccff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,11 +20,11 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.14.3 + rev: 0.16.0 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.1.13 + rev: v1.2.0 hooks: - id: forbid-tabs exclude_types: From f1ebfe94419d12ef42b7f932a651b1630a071e1e Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 5 Jun 2022 21:21:23 -0700 Subject: [PATCH 101/854] key the DF cache on the hash of download-df.sh --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ef4187be..31fde3b1d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,7 +60,7 @@ jobs: uses: actions/cache@v2 with: path: ~/DF - key: ${{ steps.env_setup.outputs.df_version }} + key: ${{ steps.env_setup.outputs.df_version }}-${{ hashFiles('ci/download-df.sh') }} - name: Fetch ccache uses: actions/cache@v2 with: From c940f086b5fea09234eb9de3e773a667c0843b99 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 6 Jun 2022 16:38:40 -0700 Subject: [PATCH 102/854] make the extra_fns functionality clearer --- .../library/test/ecosystem/in/gui_quantum-spec.csv | 2 +- test/quickfort/ecosystem.lua | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/data/blueprints/library/test/ecosystem/in/gui_quantum-spec.csv b/data/blueprints/library/test/ecosystem/in/gui_quantum-spec.csv index 91fde500d..7ba7b2ecb 100644 --- a/data/blueprints/library/test/ecosystem/in/gui_quantum-spec.csv +++ b/data/blueprints/library/test/ecosystem/in/gui_quantum-spec.csv @@ -2,4 +2,4 @@ description=integration test for the gui/quantum script width=5 height=5 -extra_fn=test_gui_quantum +extra_fn=gui_quantum diff --git a/test/quickfort/ecosystem.lua b/test/quickfort/ecosystem.lua index 39d0f6be1..716f22e01 100644 --- a/test/quickfort/ecosystem.lua +++ b/test/quickfort/ecosystem.lua @@ -10,8 +10,10 @@ -- height (required) -- depth (default is 1) -- start (cursor offset for input blueprints, default is 1,1) --- extra_fn (the name of a global function in this file to run after applying --- all blueprints but before comparing results) +-- extra_fn (the name of a function in the extra_fns table in this file to +-- run after applying all blueprints but before comparing results. +-- this function will get the map coordinate of the upper-left +-- corner of the test area passed to it) -- -- depends on blueprint, buildingplan, and dig-now plugins (as well as the -- quickfort script and anything else run in the extra_fns, of course) @@ -301,6 +303,8 @@ local function do_dig_phase(phase_data, area, spec) run_dig_now(area) end +local extra_fns = {} + function test.end_to_end() -- read in test plan local sets = get_blueprint_sets() @@ -330,7 +334,7 @@ function test.end_to_end() -- run any extra commands, if defined by the blueprint spec if spec.extra_fn then - _ENV[spec.extra_fn](area.pos) + extra_fns[spec.extra_fn](area.pos) end -- run blueprint to generate files in output dir @@ -387,7 +391,7 @@ local function send_keys(...) end end -function test_gui_quantum(pos) +function extra_fns.gui_quantum(pos) local vehicles = assign_minecarts.get_free_vehicles() local confirm_state = confirm.isEnabled() local confirm_conf = confirm.get_conf_data() From 40a6fcdd9928d482e409801078d365b41de4e5c5 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 7 Jun 2022 07:18:05 +0000 Subject: [PATCH 103/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index a24581cc5..51991bb39 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a24581cc5318bdbc6227f368f67bc03a9082c19c +Subproject commit 51991bb39224fee8111fc5edce026dcbabf078ad diff --git a/scripts b/scripts index 52e21c5e0..5ae30f01d 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 52e21c5e0c78e17acdad23f94efffb043290d5b5 +Subproject commit 5ae30f01d9d96453d6ef435d018686db4b4a76f7 From ec2f2446d2a674ffd965167626cba1e78dcd7861 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 8 Jun 2022 07:18:16 +0000 Subject: [PATCH 104/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 51991bb39..482c43cf7 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 51991bb39224fee8111fc5edce026dcbabf078ad +Subproject commit 482c43cf7bef8642fb9a85e834ffbabfd6dc4a03 diff --git a/scripts b/scripts index 5ae30f01d..a25389df2 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 5ae30f01d9d96453d6ef435d018686db4b4a76f7 +Subproject commit a25389df2a7d8f99d0dc2d5e6f298a05f88d63f4 From f146cced95452f6d1455527b1accd944923b11be Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 5 Jun 2022 13:03:38 -0700 Subject: [PATCH 105/854] Adds definition for "undeclared reference" --- plugins/debug.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/debug.cpp b/plugins/debug.cpp index 30a5028f8..4fbc48759 100644 --- a/plugins/debug.cpp +++ b/plugins/debug.cpp @@ -511,6 +511,8 @@ private: DebugManager::categorySignal_t::Connection connection_; }; +constexpr const char* FilterManager::configPath; + FilterManager::~FilterManager() { } From 0eb9eee7738db2429a4b97edc09a7f6ea2dc9596 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 5 Jun 2022 13:04:16 -0700 Subject: [PATCH 106/854] Fixes presumed typo in if statement --- plugins/embark-assistant/matcher.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index 52348ea33..e550b5ca2 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -475,7 +475,8 @@ namespace embark_assist { return; // We're at the world edge, so no incursions from the outside. } - if (!&survey_results->at(fetch_x).at(fetch_y).surveyed) { + if (!survey_results->at(fetch_x).at(fetch_y).surveyed) { + // todo: this has never been executed before /*std::cerr << hello, is anybody out there*/ *failed_match = true; return; } From f8d46a10c107881d51a863267f0ab34ab231e712 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 5 Jun 2022 13:05:14 -0700 Subject: [PATCH 107/854] Removes unhelpful inheritance --- plugins/prospector.cpp | 6 +++--- plugins/stockpiles/StockpileUtils.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 7bc8f2388..6b2079cf0 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -85,10 +85,10 @@ bool operator>(const matdata & q1, const matdata & q2) return q1.count > q2.count; } -template -struct shallower : public binary_function<_Tp, _Tp, bool> +template +struct shallower { - bool operator()(const _Tp& top, const _Tp& bottom) const + bool operator()(const Tp& top, const Tp& bottom) const { float topavg = (top.lower_z + top.upper_z)/2.0f; float btmavg = (bottom.lower_z + bottom.upper_z)/2.0f; diff --git a/plugins/stockpiles/StockpileUtils.h b/plugins/stockpiles/StockpileUtils.h index 8558485e4..f5570be92 100644 --- a/plugins/stockpiles/StockpileUtils.h +++ b/plugins/stockpiles/StockpileUtils.h @@ -52,7 +52,7 @@ static inline size_t find_plant ( const std::string &plant_id ) return linear_index ( df::global::world->raws.plants.all, &df::plant_raw::id, plant_id ); } -struct less_than_no_case: public std::binary_function< char,char,bool > +struct less_than_no_case { bool operator () (char x, char y) const { From cec8a358b5e7c34a7aa5870b762718e7958bba87 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 5 Jun 2022 13:05:30 -0700 Subject: [PATCH 108/854] Replaces deprecated code with lambdas --- plugins/uicommon.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/uicommon.h b/plugins/uicommon.h index 54a948eb2..dc57c9374 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -174,13 +174,13 @@ static inline void set_to_limit(int &value, const int maximum, const int min = 0 // trim from start static inline std::string <rim(std::string &s) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](char x){ return !std::isspace(x); })); return s; } // trim from end static inline std::string &rtrim(std::string &s) { - s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + s.erase(std::find_if(s.rbegin(), s.rend(), [](char x){ return !std::isspace(x); }).base(), s.end()); return s; } From 04058c752958712dbc035b2e41a9ad11e4489991 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 8 Jun 2022 11:47:22 -0700 Subject: [PATCH 109/854] Update matcher.cpp --- plugins/embark-assistant/matcher.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index e550b5ca2..011a65fa3 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -476,7 +476,8 @@ namespace embark_assist { } if (!survey_results->at(fetch_x).at(fetch_y).surveyed) { - // todo: this has never been executed before /*std::cerr << hello, is anybody out there*/ + // If the data has been collected, incursion processing should be performed to evaluate whether a match actually is present. + // but if it hasn't we need to return with a failed_match *failed_match = true; return; } From 62e7303c68f35660c94a4ef80583b7440545d1a3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 8 Jun 2022 17:48:14 -0400 Subject: [PATCH 110/854] Upgrade pre-commit-hooks to v4.3.0 (+ submodules) This contains a fix on Windows that checks for the executable bit according to Git, instead of according to Windows, which reduces false-positive "check-executables-have-shebangs" failures. --- .pre-commit-config.yaml | 2 +- library/xml | 2 +- scripts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d1bb4ccff..774c42a95 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: repos: # shared across repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: check-added-large-files - id: check-case-conflict diff --git a/library/xml b/library/xml index 482c43cf7..5dd972b8b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 482c43cf7bef8642fb9a85e834ffbabfd6dc4a03 +Subproject commit 5dd972b8b5823e46a1b418fe483d4913347a13b1 diff --git a/scripts b/scripts index a25389df2..5246b643a 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a25389df2a7d8f99d0dc2d5e6f298a05f88d63f4 +Subproject commit 5246b643a91e109e934b7fb75f4cf3780ecadc85 From ff373bb9e242103cacf7f93f5e0e55f1ed8b6a20 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 8 Jun 2022 16:44:06 -0700 Subject: [PATCH 111/854] use extlinks where appropriate in docs --- docs/guides/examples-guide.rst | 4 ++-- docs/guides/quickfort-library-guide.rst | 5 ++--- docs/guides/quickfort-user-guide.rst | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/guides/examples-guide.rst b/docs/guides/examples-guide.rst index 409ceaa00..d643ec15f 100644 --- a/docs/guides/examples-guide.rst +++ b/docs/guides/examples-guide.rst @@ -91,8 +91,8 @@ order to save resources. It handles: - adamantine processing - item melting -Orders are missing for plaster powder until DF `bug 11803 -`_ is fixed. +Orders are missing for plaster powder until DF :bug:`bug 11803 <11803>` is +fixed. :source:`military.json ` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/guides/quickfort-library-guide.rst b/docs/guides/quickfort-library-guide.rst index 2f9b3a06f..ab80bab5f 100644 --- a/docs/guides/quickfort-library-guide.rst +++ b/docs/guides/quickfort-library-guide.rst @@ -49,9 +49,8 @@ and a convenient `checklist `__ from which you can copy the ``quickfort`` commands. -If you like, you can download a fully built Dreamfort-based fort from `dffd -`__, load it, and explore it -interactively. +If you like, you can download a fully built Dreamfort-based fort from +:dffd:`dffd <15434>`, load it, and explore it interactively. Visual overview ``````````````` diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index a886773f9..e92b7d3e8 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -1462,8 +1462,7 @@ parts that might not be obvious just from looking at them. If you haven't built Dreamfort before, maybe try an embark in a flat area and take it for a spin! It will help put the following sections in context. There is also a pre-built Dreamfort available for download on -`dffd `__ if you just want an -interactive reference. +:dffd:`dffd <15434>` if you just want an interactive reference. Dreamfort organization and packaging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 698dc562223ce33cc7f14aa3cd9b2febd47b9f81 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 5 Jun 2022 19:20:02 -0400 Subject: [PATCH 112/854] GitHub Actions: Upgrade newest matrix entry to Ubuntu 22.04, GCC 12 --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31fde3b1d..285a44578 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,9 +17,12 @@ jobs: plugins: - default include: - - os: ubuntu-20.04 + - os: ubuntu-22.04 gcc: 11 plugins: all + - os: ubuntu-22.04 + gcc: 12 + plugins: all steps: - name: Set up Python 3 uses: actions/setup-python@v2 From 4152be13c70758d0d8d0385e6ec6f6294543458e Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 8 Jun 2022 19:02:58 -0700 Subject: [PATCH 113/854] Simplify bug extlink --- docs/guides/examples-guide.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/guides/examples-guide.rst b/docs/guides/examples-guide.rst index d643ec15f..8ea463d8c 100644 --- a/docs/guides/examples-guide.rst +++ b/docs/guides/examples-guide.rst @@ -91,8 +91,7 @@ order to save resources. It handles: - adamantine processing - item melting -Orders are missing for plaster powder until DF :bug:`bug 11803 <11803>` is -fixed. +Orders are missing for plaster powder until DF :bug:`11803` is fixed. :source:`military.json ` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 3f6b30af15dc023341e711f0096788763b3b7785 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 8 Jun 2022 22:01:52 -0400 Subject: [PATCH 114/854] Update stonesense --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 290862549..841803ca6 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 2908625499accc4899e9a9f0e999c95be26ce14d +Subproject commit 841803ca6f173f4d97eea376b4dcd512336e35e2 From e3f617d98e59363daca706a36f26afc0c1f89d01 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 8 Jun 2022 22:18:13 -0400 Subject: [PATCH 115/854] build.yml: Force CMake to use ccache CMake in the Ubuntu 22.04 environment doesn't seem to pick up on ccache implicitly anymore. --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 285a44578..bbb44a2cd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,6 +90,8 @@ jobs: -DBUILD_SIZECHECK:BOOL=${{ matrix.plugins == 'all' }} \ -DBUILD_STONESENSE:BOOL=${{ matrix.plugins == 'all' }} \ -DBUILD_SUPPORTED:BOOL=1 \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" - name: Build DFHack run: | From b3618810c37afa5acc04f5d906e52271ba51ba4f Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 8 Jun 2022 22:21:48 -0400 Subject: [PATCH 116/854] Add ccache stats to build output --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bbb44a2cd..02e6709fb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -96,6 +96,7 @@ jobs: - name: Build DFHack run: | ninja -C build-ci install + ccache --show-stats - name: Run tests id: run_tests run: | From fbdeba62168a83dbce81894f5ad086329f815458 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 9 Jun 2022 01:02:37 -0400 Subject: [PATCH 117/854] Force consistent ccache cache directory This changed to use ~/.cache/ccache instead of ~/.ccache in ccache 4. Squashed: fixed to expand `$HOME` in a shell, because `~` in an `env:` block does not get expanded. --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02e6709fb..e48aaad06 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,6 +59,7 @@ jobs: echo "::set-output name=df_version::${DF_VERSION}" echo "DF_VERSION=${DF_VERSION}" >> $GITHUB_ENV echo "DF_FOLDER=${HOME}/DF/${DF_VERSION}/df_linux" >> $GITHUB_ENV + echo "CCACHE_DIR=${HOME}/.ccache" >> $GITHUB_ENV - name: Fetch DF cache uses: actions/cache@v2 with: From 078256cc2a7720da5a059c6a00cd045434447f57 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 9 Jun 2022 01:35:55 -0400 Subject: [PATCH 118/854] Remove GCC 11 from build matrix --- .github/workflows/build.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e48aaad06..21b3096d3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,9 +17,6 @@ jobs: plugins: - default include: - - os: ubuntu-22.04 - gcc: 11 - plugins: all - os: ubuntu-22.04 gcc: 12 plugins: all From d12134d326351eecef86a6e694153a4152f4899c Mon Sep 17 00:00:00 2001 From: Myk Date: Thu, 9 Jun 2022 11:21:14 -0700 Subject: [PATCH 119/854] check for files, not md5 receipt in download-df.sh (#2196) --- .github/workflows/build.yml | 2 +- ci/download-df.sh | 19 +++---------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 21b3096d3..2c710d91a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,7 +61,7 @@ jobs: uses: actions/cache@v2 with: path: ~/DF - key: ${{ steps.env_setup.outputs.df_version }}-${{ hashFiles('ci/download-df.sh') }} + key: dfcache-${{ steps.env_setup.outputs.df_version }}-${{ hashFiles('ci/download-df.sh') }} - name: Fetch ccache uses: actions/cache@v2 with: diff --git a/ci/download-df.sh b/ci/download-df.sh index 2fd095c1d..bb1e91759 100755 --- a/ci/download-df.sh +++ b/ci/download-df.sh @@ -5,9 +5,6 @@ set -e df_tardest="df.tar.bz2" save_tardest="test_save.tgz" -selfmd5=$(openssl md5 < "$0") -echo $selfmd5 - cd "$(dirname "$0")" echo "DF_VERSION: $DF_VERSION" echo "DF_FOLDER: $DF_FOLDER" @@ -15,17 +12,7 @@ mkdir -p "$DF_FOLDER" # back out of df_linux cd "$DF_FOLDER/.." -if [ -f receipt ]; then - if [ "$selfmd5" != "$(cat receipt)" ]; then - echo "download-df.sh changed; re-downloading tarballs" - rm receipt - else - echo "Already downloaded $DF_VERSION tarballs" - fi -fi - -if [ ! -f receipt ]; then - rm -f "$df_tardest" "$save_tardest" +if ! test -f "$df_tardest"; then minor=$(echo "$DF_VERSION" | cut -d. -f2) patch=$(echo "$DF_VERSION" | cut -d. -f3) echo "Downloading DF $DF_VERSION" @@ -43,6 +30,7 @@ URLS echo "DF failed to download: $df_tardest not found" exit 1 fi + echo "Downloading test save" #test_save_url="https://files.dfhack.org/DF/0.${minor}.${patch}/test_save.tgz" test_save_url="https://drive.google.com/uc?export=download&id=1XvYngl-DFONiZ9SD9OC4B2Ooecu8rPFz" @@ -61,5 +49,4 @@ tar xf "$df_tardest" --strip-components=1 -C df_linux tar xf "$save_tardest" -C df_linux/data/save echo Done -echo "$selfmd5" > receipt -ls +ls -l From 1f38936723a3e8443f025288bcf06f5a316713bb Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 10 Jun 2022 13:35:25 -0700 Subject: [PATCH 120/854] don't create the unused manipulator/ directory --- plugins/manipulator.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 5604b9760..4604cd7ea 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -53,8 +53,6 @@ REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(enabler); -#define CONFIG_PATH "manipulator" - struct SkillLevel { const char *name; @@ -2307,11 +2305,6 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - if (!Filesystem::isdir(CONFIG_PATH) && !Filesystem::mkdir(CONFIG_PATH)) - { - out.printerr("manipulator: Could not create configuration folder: \"%s\"\n", CONFIG_PATH); - return CR_FAILURE; - } return CR_OK; } From d846a1c93f8cd8ac5a76ed4688c3f655a5836e23 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 10 Jun 2022 22:48:30 +0000 Subject: [PATCH 121/854] Auto-update submodules scripts: master depends/xlsxio: dfhack --- depends/xlsxio | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/xlsxio b/depends/xlsxio index ab8fd7f3e..439fdbc25 160000 --- a/depends/xlsxio +++ b/depends/xlsxio @@ -1 +1 @@ -Subproject commit ab8fd7f3e9df457e8bc1b5cb31b78d57df0ac5a4 +Subproject commit 439fdbc259c13f23a3122e68ba35ad5a13bcd97c diff --git a/scripts b/scripts index 5246b643a..791748739 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 5246b643a91e109e934b7fb75f4cf3780ecadc85 +Subproject commit 791748739ada792591995585a0c8218ea87402ec From 4de5b89fa3ff23fed11db1231a1100628a3c94d9 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 10 Jun 2022 15:59:19 -0700 Subject: [PATCH 122/854] don't try to build up stairs on flat ground --- data/blueprints/library/dreamfort.csv | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index 9ac0f58d2..296e6c88b 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -334,10 +334,11 @@ corridor/surface_corridor message(Once the central stairs are mined out deeply enough, you should start digging the industry level in a non-aquifer rock layer. You'll need the boulders from the digging to make blocks. If your wagon is within the fort perimeter, deconstruct it to get it out of the way. Once the marked trees are all chopped down (if any), continue with /surface2.) clear trees and set up pastures" -central_stairs/central_stairs repeat(down 10) clear_small/surface_clear_small zones/surface_zones name_zones/surface_name_zones +#> +central_stairs/central_stairs repeat(down 10) "" "#meta label(surface2) start(central stairs) message(Remember to enqueue manager orders for this blueprint. Once the channels are dug out and the marked trees are cleared, continue with /surface3.) set up starting workshops/stockpiles, channel miasma vents, and clear more trees" @@ -387,9 +388,9 @@ corridor/surface_corridor corridor_traps/surface_corridor_traps query_corridor/surface_query_corridor #dig label(central_stairs_odd) start(2;2) hidden() spiral stairs odd levels -`,u,` -j6,`,j6 -`,u,` +`,j6,` +u,`,u +`,j6,` #meta label(central_stairs_even) hidden() spiral stairs even levels /central_stairs_odd transform(cw) #meta label(central_stairs) two levels of spiral stairs (use --repeat down) @@ -413,11 +414,11 @@ j6,`,j6 ,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` ,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` ,,,`,,`,`,`,`,`,t1,`,`,`,`,`,`,,`,,`,`,`,`,`,t1,`,`,`,t1,`,`,,` -,,,`,,`,,`,t1,t1,,,,,,`,,,,,,`,,,,,,,,,,`,,` -,,,`,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,` -,,,`,,`,,,,,,,,,,`,,`,~,`,,`,,,,,,,,,,`,,` -,,,`,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,` -,,,`,,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,,` +,,,`,,`,,`,t1,t1,,,,,,`,t1,t1,t1,t1,t1,`,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,,t1,t1,t1,t1,t1,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,`,t1,j,t1,j,t1,`,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,,t1,t1,t1,t1,t1,,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,`,t1,t1,t1,t1,t1,`,,,,,,,,,,`,,` ,,,`,,`,`,`,`,`,`,`,,`,`,`,`,,`,,`,`,`,`,,`,`,`,`,`,`,`,,` ,,,`,,`,,,,,,,,,`,t1,,,,,,t1,`,,,,,,,,,`,,` ,,,`,,`,,,,,,,,,t1,t1,,,,,,t1,t1,,,,,,,,,`,,` From 98112050c309231f4cfa47eeb60dce4f6c421553 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 11 Jun 2022 07:16:22 +0000 Subject: [PATCH 123/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 791748739..4941cacca 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 791748739ada792591995585a0c8218ea87402ec +Subproject commit 4941caccab6dd775d43250e79b8d1a75b8a24b57 From 85d7489b3c66e3f61b418fe138b22da1da2ce032 Mon Sep 17 00:00:00 2001 From: Myk Date: Sat, 11 Jun 2022 07:38:22 -0700 Subject: [PATCH 124/854] ensure refs are cleaned up when we remove a job (#2184) * ensure job items are disassociated from the job when the job is removed. the new df-provided ``cancel_job()`` doesn't do this for us whereas the old custom implementation did. ref: #2028 * remove trailing whitespace * Clean up general refs before removing job Because the game method doesn't do it itself * Fix typo in var name * clean up code * update changelog --- docs/changelog.txt | 1 + library/modules/Job.cpp | 38 ++++++++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 0e3c90855..57cd2f6f6 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -39,6 +39,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - ``widgets.CycleHotkeyLabel``: allow initial option values to be specified as an index instead of an option value +- ``job.removeJob()``: fixes regression in DFHack 0.47.05-r5 where items/buildings associated with the job were not getting disassociated when the job is removed. Now `build-now` can build buildings and `gui/mass-remove` can cancel building deconstruction again ## Misc Improvements - `confirm`: added a confirmation dialog for removing manager orders diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index ac800cffc..749be199e 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -298,10 +298,10 @@ void DFHack::Job::setJobCooldown(df::building *workshop, df::unit *worker, int c } } -void DFHack::Job::disconnectJobItem(df::job *job, df::job_item_ref *ref) { - if (!ref) return; +void DFHack::Job::disconnectJobItem(df::job *job, df::job_item_ref *item_ref) { + if (!item_ref) return; - auto item = ref->item; + auto item = item_ref->item; if (!item) return; //Work backward through the specific refs & remove/delete all specific refs to this job @@ -360,10 +360,36 @@ bool DFHack::Job::removeJob(df::job* job) { using df::global::world; CHECK_NULL_POINTER(job); - // call the job cancel vmethod graciously provided by The Toady One. - // job_handler::cancel_job calls job::~job, and then deletes job (this has been confirmed by disassembly) - // this method cannot fail; it will either delete the job or crash/corrupt DF + // cancel_job below does not clean up refs, so we have to do that first + + // clean up general refs + for (auto genRef : job->general_refs) { + if (!genRef) continue; + + // disconnectJobGeneralRef only handles buildings and units + if (genRef->getType() != general_ref_type::BUILDING_HOLDER && + genRef->getType() != general_ref_type::UNIT_WORKER) + return false; + } + for (auto genRef : job->general_refs) { + // this should always succeed because of the check in the preceding loop + bool success = disconnectJobGeneralRef(job, genRef); + assert(success); (void)success; + if (genRef) delete genRef; + } + job->general_refs.resize(0); + + // clean up item refs + for (auto &item_ref : job->items) { + disconnectJobItem(job, item_ref); + if (item_ref) delete item_ref; + } + job->items.resize(0); + + // call the job cancel vmethod graciously provided by The Toady One. + // job_handler::cancel_job calls job::~job, and then deletes job (this has + // been confirmed by disassembly). world->jobs.cancel_job(job); return true; From 1a629a26bf895f93f5fdb54f328dbd3456deebe2 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 12 Jun 2022 16:03:04 +0200 Subject: [PATCH 125/854] add `ItemTraitsDialog` to materials.lua (#2199) * add `ItemTraitDialog` to materials.lua * rename `ItemTraitDialog` to `ItemTraitsDialog` (plural) * Update changelog.txt --- docs/changelog.txt | 1 + library/lua/gui/materials.lua | 262 ++++++++++++++++++++++++++++++++++ 2 files changed, 263 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 57cd2f6f6..4c034b5bc 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -66,6 +66,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``widgets.EditField``: the ``key_sep`` string is now configurable - ``widgets.EditField``: can now display an optional string label in addition to the activation key - ``widgets.EditField``: views that have an ``EditField`` subview no longer need to manually manage the ``EditField`` activation state and input routing. This is now handled automatically by the new ``gui.View`` keyboard focus subsystem. +- ``materials.ItemTraitsDialog``: new dialog to edit item traits (where "item" is part of a job or work order or similar). The list of traits is the same as in vanilla work order conditions "``t`` change traits". # 0.47.05-r5 diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index 8894b36af..c9eaaf38d 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -343,4 +343,266 @@ function ItemTypeDialog(args) return dlg.ListBox(args) end +function ItemTraitsDialog(args) + local job_item_flags_map = {} + for i = 1, 3 do + for _, f in ipairs(df['job_item_flags'..i]) do + if f then + job_item_flags_map[f] = 'flags'..i + end + end + end + local job_item_flags = {} + for k, _ in pairs(job_item_flags_map) do + job_item_flags[#job_item_flags + 1] = k + end + table.sort(job_item_flags) + -------------------------------------- + local tool_uses = {} + for i, _ in ipairs(df.tool_uses) do + tool_uses[#tool_uses + 1] = df.tool_uses[i] + end + local restore_none = false + if tool_uses[1] == 'NONE' then + restore_none = true + table.remove(tool_uses, 1) + end + table.sort(tool_uses) + if restore_none then + table.insert(tool_uses, 1, 'NONE') + end + -------------------------------------- + local set_ore_ix = {} + for i, raw in ipairs(df.global.world.raws.inorganics) do + for _, ix in ipairs(raw.metal_ore.mat_index) do + set_ore_ix[ix] = true + end + end + local ores = {} + for ix in pairs(set_ore_ix) do + local raw = df.global.world.raws.inorganics[ix] + ores[#ores+1] = {mat_index = ix, name = raw.material.state_name.Solid} + end + table.sort(ores, function(a,b) return a.name < b.name end) + + -------------------------------------- + -- CALCIUM_CARBONATE, CAN_GLAZE, FAT, FLUX, + -- GYPSUM, PAPER_PLANT, PAPER_SLURRY, TALLOW, WAX + local reaction_classes_set = {} + for ix,reaction in ipairs(df.global.world.raws.reactions.reactions) do + if #reaction.reagents > 0 then + for _, r in ipairs(reaction.reagents) do + if r.reaction_class and r.reaction_class ~= '' then + reaction_classes_set[r.reaction_class] = true + end + end + end --if + end + local reaction_classes = {} + for k in pairs(reaction_classes_set) do + reaction_classes[#reaction_classes + 1] = k + end + table.sort(reaction_classes) + -------------------------------------- + -- PRESS_LIQUID_MAT, TAN_MAT, BAG_ITEM etc + local product_materials_set = {} + for ix,reaction in ipairs(df.global.world.raws.reactions.reactions) do + if #reaction.products > 0 then + --for _, p in ipairs(reaction.products) do + -- in the list in work order conditions there is no SEED_MAT. + -- I think it's because the game doesn't iterate over all products. + local p = reaction.products[0] + local mat = p.get_material + if mat and mat.product_code ~= '' then + product_materials_set[mat.product_code] = true + end + --end + end --if + end + local product_materials = {} + for k in pairs(product_materials_set) do + product_materials[#product_materials + 1] = k + end + table.sort(product_materials) + --==================================-- + + local function set_search_keys(choices) + for _, choice in ipairs(choices) do + if not choice.search_key then + if type(choice.text) == 'table' then + local search_key = {} + for _, token in ipairs(choice.text) do + search_key[#search_key+1] = string.lower(token.text or '') + end + choice.search_key = table.concat(search_key, ' ') + elseif choice.text then + choice.search_key = string.lower(choice.text) + end + end + end + end + + args.text = args.prompt or 'Type or select an item trait' + args.text_pen = COLOR_WHITE + args.with_filter = true + args.icon_width = 2 + args.dismiss_on_select = false + + local pen_active = COLOR_LIGHTCYAN + local pen_active_d = COLOR_CYAN + local pen_not_active = COLOR_LIGHTRED + local pen_not_active_d = COLOR_RED + local pen_action = COLOR_WHITE + local pen_action_d = COLOR_GREY + + local job_item = args.job_item + local choices = {} + + local pen_cb = function(args, fnc) + if not (args and fnc) then + return COLOR_YELLOW + end + return fnc(args) and pen_active or pen_not_active + end + local pen_d_cb = function(args, fnc) + if not (args and fnc) then + return COLOR_YELLOW + end + return fnc(args) and pen_active_d or pen_not_active_d + end + local icon_cb = function(args, fnc) + if not (args and fnc) then + return '\19' -- ‼ + end + -- '\251' is a checkmark + -- '\254' is a square + return fnc(args) and '\251' or '\254' + end + + if not args.hide_none then + table.insert(choices, { + icon = '!', + text = {{text = args.none_caption or 'none', pen = pen_action, dpen = pen_action_d}}, + reset_all_traits = true + }) + end + + local isActiveFlag = function (obj) + return obj.job_item[obj.ffield][obj.flag] + end + table.insert(choices, { + icon = '!', + text = {{text = 'unset flags', pen = pen_action, dpen = pen_action_d}}, + reset_flags = true + }) + for _, flag in ipairs(job_item_flags) do + local ffield = job_item_flags_map[flag] + local text = 'is ' .. (value and '' or 'any ') .. string.lower(flag) + local args = {job_item=job_item, ffield=ffield, flag=flag} + table.insert(choices, { + icon = curry(icon_cb, args, isActiveFlag), + text = {{text = text, + pen = curry(pen_cb, args, isActiveFlag), + dpen = curry(pen_d_cb, args, isActiveFlag), + }}, + ffield = ffield, flag = flag + }) + end + + local isActiveTool = function (args) + return df.tool_uses[args.tool_use] == args.job_item.has_tool_use + end + for _, tool_use in ipairs(tool_uses) do + if tool_use == 'NONE' then + table.insert(choices, { + icon = '!', + text = {{text = 'unset use', pen = pen_action, dpen = pen_action_d}}, + tool_use = tool_use + }) + else + local args = {job_item = job_item, tool_use=tool_use} + table.insert(choices, { + icon = ' ', + text = {{text = 'has use ' .. tool_use, + pen = curry(pen_cb, args, isActiveTool), + dpen = curry(pen_d_cb, args, isActiveTool), + }}, + tool_use = tool_use + }) + end + end + + local isActiveOre = function(args) + return (args.job_item.metal_ore == args.mat_index) + end + table.insert(choices, { + icon = '!', + text = {{text = 'unset ore', pen = pen_action, dpen = pen_action_d}}, + ore_ix = -1 + }) + for _, ore in ipairs(ores) do + local args = {job_item = job_item, mat_index=ore.mat_index} + table.insert(choices, { + icon = ' ', + text = {{text = 'ore of ' .. ore.name, + pen = curry(pen_cb, args, isActiveOre), + dpen = curry(pen_d_cb, args, isActiveOre), + }}, + ore_ix = ore.mat_index + }) + end + + local isActiveReactionClass = function(args) + return (args.job_item.reaction_class == args.reaction_class) + end + table.insert(choices, { + icon = '!', + text = {{text = 'unset reaction class', pen = pen_action, dpen = pen_action_d}}, + reaction_class = '' + }) + for _, reaction_class in ipairs(reaction_classes) do + local args = {job_item = job_item, reaction_class=reaction_class} + table.insert(choices, { + icon = ' ', + text = {{text = 'reaction class ' .. reaction_class, + pen = curry(pen_cb, args, isActiveReactionClass), + dpen = curry(pen_d_cb, args, isActiveReactionClass) + }}, + reaction_class = reaction_class + }) + end + + local isActiveProduct = function(args) + return (args.job_item.has_material_reaction_product == args.product_materials) + end + table.insert(choices, { + icon = '!', + text = {{text = 'unset producing', pen = pen_action, dpen = pen_action_d}}, + product_materials = '' + }) + for _, product_materials in ipairs(product_materials) do + local args = {job_item = job_item, product_materials=product_materials} + table.insert(choices, { + icon = ' ', + text = {{text = product_materials .. '-producing', + pen = curry(pen_cb, args, isActiveProduct), + dpen = curry(pen_d_cb, args, isActiveProduct) + }}, + product_materials = product_materials + }) + end + + set_search_keys(choices) + args.choices = choices + + if args.on_select then + local cb = args.on_select + args.on_select = function(idx, obj) + return cb(obj) + end + end + + return dlg.ListBox(args) +end + return _ENV From fc384fd1a2bee65315fe79831a55ca74c8e6717b Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 12 Jun 2022 17:12:49 +0300 Subject: [PATCH 126/854] Update eventful.lua (#2203) * Update eventful.lua Had wrong function. Fixes https://github.com/DFHack/dfhack/issues/2202 * Update Lua API.rst Update docs to add onReactionCompleting and remove outdated info * Update Lua API.rst Some more minor doc fixes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add changelog entry Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/Lua API.rst | 27 ++++++++++++++++----------- docs/changelog.txt | 1 + plugins/lua/eventful.lua | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index bbcf0b145..55a217757 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4587,35 +4587,40 @@ on DF world events. List of events -------------- -1. ``onReactionComplete(reaction,reaction_product,unit,input_items,input_reagents,output_items,call_native)`` +1. ``onReactionCompleting(reaction,reaction_product,unit,input_items,input_reagents,output_items,call_native)`` - Auto activates if detects reactions starting with ``LUA_HOOK_``. Is called when reaction finishes. + Is called once per reaction product, before reaction had a chance to call native code for item creation. + Setting ``call_native.value=false`` cancels further processing: no items are created and ``onReactionComplete`` is not called. -2. ``onItemContaminateWound(item,unit,wound,number1,number2)`` +2. ``onReactionComplete(reaction,reaction_product,unit,input_items,input_reagents,output_items)`` + + Is called once per reaction product, when reaction finishes and has at least one product. + +3. ``onItemContaminateWound(item,unit,wound,number1,number2)`` Is called when item tries to contaminate wound (e.g. stuck in). -3. ``onProjItemCheckMovement(projectile)`` +4. ``onProjItemCheckMovement(projectile)`` Is called when projectile moves. -4. ``onProjItemCheckImpact(projectile,somebool)`` +5. ``onProjItemCheckImpact(projectile,somebool)`` Is called when projectile hits something. -5. ``onProjUnitCheckMovement(projectile)`` +6. ``onProjUnitCheckMovement(projectile)`` Is called when projectile moves. -6. ``onProjUnitCheckImpact(projectile,somebool)`` +7. ``onProjUnitCheckImpact(projectile,somebool)`` Is called when projectile hits something. -7. ``onWorkshopFillSidebarMenu(workshop,callnative)`` +8. ``onWorkshopFillSidebarMenu(workshop,callnative)`` Is called when viewing a workshop in 'q' mode, to populate reactions, useful for custom viewscreens for shops. -8. ``postWorkshopFillSidebarMenu(workshop)`` +9. ``postWorkshopFillSidebarMenu(workshop)`` Is called after calling (or not) native fillSidebarMenu(). Useful for job button tweaking (e.g. adding custom reactions) @@ -4683,7 +4688,7 @@ Functions 1. ``registerReaction(reaction_name,callback)`` - Simplified way of using onReactionComplete; the callback is function (same params as event). + Simplified way of using onReactionCompleting; the callback is function (same params as event). 2. ``removeNative(shop_name)`` @@ -4715,7 +4720,7 @@ Reaction complete example:: b=require "plugins.eventful" - b.registerReaction("LUA_HOOK_LAY_BOMB",function(reaction,unit,in_items,in_reag,out_items,call_native) + b.registerReaction("LAY_BOMB",function(reaction,unit,in_items,in_reag,out_items,call_native) local pos=copyall(unit.pos) -- spawn dragonbreath after 100 ticks dfhack.timeout(100,"ticks",function() dfhack.maps.spawnFlow(pos,6,0,0,50000) end) diff --git a/docs/changelog.txt b/docs/changelog.txt index 4c034b5bc..1302cf82e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -40,6 +40,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - ``widgets.CycleHotkeyLabel``: allow initial option values to be specified as an index instead of an option value - ``job.removeJob()``: fixes regression in DFHack 0.47.05-r5 where items/buildings associated with the job were not getting disassociated when the job is removed. Now `build-now` can build buildings and `gui/mass-remove` can cancel building deconstruction again +- `eventful`: fix ``eventful.registerReaction`` to correctly pass ``call_native`` argument thus allowing canceling vanilla item creation. Updated related documentation. ## Misc Improvements - `confirm`: added a confirmation dialog for removing manager orders diff --git a/plugins/lua/eventful.lua b/plugins/lua/eventful.lua index 293874393..b1520dac1 100644 --- a/plugins/lua/eventful.lua +++ b/plugins/lua/eventful.lua @@ -93,7 +93,7 @@ end function registerReaction(reaction_name,callback) _registeredStuff.reactionCallbacks=_registeredStuff.reactionCallbacks or {} _registeredStuff.reactionCallbacks[reaction_name]=callback - onReactionComplete._library=onReact + onReactionCompleting._library=onReact dfhack.onStateChange.eventful=unregall end From ae5b00523ead243c1f319453f6e69a533c04b367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82?= Date: Tue, 14 Jun 2022 06:35:59 +0200 Subject: [PATCH 127/854] Add myself to Authors.rst (#2208) As suggested in: https://github.com/DFHack/scripts/pull/404#issuecomment-1152780958 --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index afb0a3f14..2369d393d 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -134,6 +134,7 @@ potato Priit Laes plaes Putnam Putnam3145 Quietust quietust _Q +Rafał Karczmarczyk CarabusX Raidau Raidau Ralph Bisschops ralpha Ramblurr Ramblurr From d38ab1d152892ccdd3b53e0303ff2266bdf1ff46 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 17 Jun 2022 09:34:41 -0700 Subject: [PATCH 128/854] Fix UNIT_NEW_ACTIVE events (#2197) * Updates eventful.lua to use UNIT_NEW_ACTIVE * Fixes bug #2189 * Revises activeUnits declaration/initialization * Fixes build error * Update changelog.txt * reword changelog entry * add changelog entry for event name change Co-authored-by: Myk --- docs/changelog.txt | 2 ++ library/modules/EventManager.cpp | 9 ++++++++- plugins/lua/eventful.lua | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 1302cf82e..4aa3e85c9 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -38,6 +38,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## New Tweaks ## Fixes +- `eventful`: fixed UNIT_NEW_ACTIVE event firing too often - ``widgets.CycleHotkeyLabel``: allow initial option values to be specified as an index instead of an option value - ``job.removeJob()``: fixes regression in DFHack 0.47.05-r5 where items/buildings associated with the job were not getting disassociated when the job is removed. Now `build-now` can build buildings and `gui/mass-remove` can cancel building deconstruction again - `eventful`: fix ``eventful.registerReaction`` to correctly pass ``call_native`` argument thus allowing canceling vanilla item creation. Updated related documentation. @@ -49,6 +50,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `dfhack-examples-guide`: reduce required stock of dye for "Dye cloth" orders - `dfhack-examples-guide`: fix material conditions for making jugs and pots - `dfhack-examples-guide`: make wooden jugs by default to differentiate them from other stone tools. this allows players to more easily select jugs out with a properly-configured stockpile (i.e. the new ``woodentools`` alias) +- `eventful`: renamed NEW_UNIT_ACTIVE event to UNIT_NEW_ACTIVE to match the ``EventManager`` event name - `quickfort-alias-guide`: new aliases: ``forbidsearch``, ``permitsearch``, and ``togglesearch`` use the `search-plugin` plugin to alter the settings for a filtered list of item types when configuring stockpiles - `quickfort-alias-guide`: new aliases: ``stonetools`` and ``woodentools``. the ``jugs`` alias is deprecated. please use ``stonetools`` instead, which is the same as the old ``jugs`` alias. - `quickfort-alias-guide`: new aliases: ``usablehair``, ``permitusablehair``, and ``forbidusablehair`` alter settings for the types of hair/wool that can be made into cloth: sheep, llama, alpaca, and troll. The ``craftrefuse`` aliases have been altered to use this alias as well. diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 70908aada..5d2a24ad0 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -199,6 +199,9 @@ static int32_t lastJobId = -1; //job completed static unordered_map prevJobs; +//active units +static unordered_set activeUnits; + //unit death static unordered_set livingUnits; @@ -256,6 +259,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event buildings.clear(); constructions.clear(); equipmentLog.clear(); + activeUnits.clear(); Buildings::clearBuildings(out); lastReport = -1; @@ -314,6 +318,9 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event } lastSyndromeTime = -1; for (auto unit : df::global::world->units.all) { + if (Units::isActive(unit)) { + activeUnits.emplace(unit->id); + } for (auto syndrome : unit->syndromes.active) { int32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time; if ( startTime > lastSyndromeTime ) @@ -592,7 +599,6 @@ static void manageNewUnitActiveEvent(color_ostream& out) { if (!df::global::world) return; - static unordered_set activeUnits; multimap copy(handlers[EventType::UNIT_NEW_ACTIVE].begin(), handlers[EventType::UNIT_NEW_ACTIVE].end()); // iterate event handler callbacks for (auto &key_value : copy) { @@ -600,6 +606,7 @@ static void manageNewUnitActiveEvent(color_ostream& out) { for (df::unit* unit : df::global::world->units.active) { int32_t id = unit->id; if (!activeUnits.count(id)) { + activeUnits.emplace(id); handler.eventHandler(out, (void*) intptr_t(id)); // intptr_t() avoids cast from smaller type warning } } diff --git a/plugins/lua/eventful.lua b/plugins/lua/eventful.lua index b1520dac1..1e3170c45 100644 --- a/plugins/lua/eventful.lua +++ b/plugins/lua/eventful.lua @@ -154,7 +154,7 @@ eventType=invertTable{ "JOB_INITIATED", "JOB_STARTED", "JOB_COMPLETED", - "NEW_UNIT_ACTIVE", + "UNIT_NEW_ACTIVE", "UNIT_DEATH", "ITEM_CREATED", "BUILDING", From b361a66a52d4eefcd368f601383c8e92d8e07191 Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 17 Jun 2022 09:35:31 -0700 Subject: [PATCH 129/854] ensure our docs build cleanly with sphinx-5 (#2193) * ensure our docs build cleanly with sphinx-5 * adapt to the API change in sphinx 5 while keeping compatibility with <5 * get rid of the extra colons in field lists --- conf.py | 31 ++++++++++++++++++++++--------- docs/styles/sphinx5.css | 3 +++ 2 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 docs/styles/sphinx5.css diff --git a/conf.py b/conf.py index c762a3cfa..79b873bf9 100644 --- a/conf.py +++ b/conf.py @@ -19,6 +19,7 @@ from io import open import os import re import shlex # pylint:disable=unused-import +import sphinx import sys @@ -28,6 +29,7 @@ import sys from docutils import nodes from docutils.parsers.rst import roles +sphinx_major_version = sphinx.version_info[0] def get_keybinds(): """Get the implemented keybinds, and return a dict of @@ -197,20 +199,28 @@ extensions = [ 'dfhack.lexer', ] +def get_caption_str(prefix=''): + return prefix + (sphinx_major_version >= 5 and '%s' or '') + # This config value must be a dictionary of external sites, mapping unique # short alias names to a base URL and a prefix. # See http://sphinx-doc.org/ext/extlinks.html extlinks = { - 'wiki': ('https://dwarffortresswiki.org/%s', ''), + 'wiki': ('https://dwarffortresswiki.org/%s', get_caption_str()), 'forums': ('http://www.bay12forums.com/smf/index.php?topic=%s', - 'Bay12 forums thread '), - 'dffd': ('https://dffd.bay12games.com/file.php?id=%s', 'DFFD file '), + get_caption_str('Bay12 forums thread ')), + 'dffd': ('https://dffd.bay12games.com/file.php?id=%s', + get_caption_str('DFFD file ')), 'bug': ('https://www.bay12games.com/dwarves/mantisbt/view.php?id=%s', - 'Bug '), - 'source': ('https://github.com/DFHack/dfhack/tree/develop/%s', ''), - 'source-scripts': ('https://github.com/DFHack/scripts/tree/master/%s', ''), - 'issue': ('https://github.com/DFHack/dfhack/issues/%s', 'Issue '), - 'commit': ('https://github.com/DFHack/dfhack/commit/%s', 'Commit '), + get_caption_str('Bug ')), + 'source': ('https://github.com/DFHack/dfhack/tree/develop/%s', + get_caption_str()), + 'source-scripts': ('https://github.com/DFHack/scripts/tree/master/%s', + get_caption_str()), + 'issue': ('https://github.com/DFHack/dfhack/issues/%s', + get_caption_str('Issue ')), + 'commit': ('https://github.com/DFHack/dfhack/commit/%s', + get_caption_str('Commit ')), } # Add any paths that contain templates here, relative to this directory. @@ -259,7 +269,7 @@ version = release = get_version() # for a list of supported languages. # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # strftime format for |today| and 'Last updated on:' timestamp at page bottom today_fmt = html_last_updated_fmt = '%Y-%m-%d' @@ -355,6 +365,9 @@ html_css_files = [ 'dfhack.css', ] +if sphinx_major_version >= 5: + html_css_files.append('sphinx5.css') + # -- Options for LaTeX output --------------------------------------------- # Grouping the document tree into LaTeX files. List of tuples diff --git a/docs/styles/sphinx5.css b/docs/styles/sphinx5.css new file mode 100644 index 000000000..385569510 --- /dev/null +++ b/docs/styles/sphinx5.css @@ -0,0 +1,3 @@ +dl.field-list > dt:after { + display: none; +} From 88ae50b9aeb87caed87c3a331fd6b360ad969605 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 17 Jun 2022 15:42:23 -0700 Subject: [PATCH 130/854] update version and changelog to 0.47.05-r6 --- CMakeLists.txt | 2 +- docs/changelog.txt | 26 ++++++++++++++++++-------- library/xml | 2 +- scripts | 2 +- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7cbf13789..8271840e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,7 +192,7 @@ endif() # set up versioning. set(DF_VERSION "0.47.05") -set(DFHACK_RELEASE "r5") +set(DFHACK_RELEASE "r6") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/docs/changelog.txt b/docs/changelog.txt index 4aa3e85c9..e91a427a9 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -38,10 +38,23 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## New Tweaks ## Fixes + +## Misc Improvements + +## Documentation + +## API + +## Lua + +# 0.47.05-r6 + +## Fixes +- `eventful`: fix ``eventful.registerReaction`` to correctly pass ``call_native`` argument thus allowing canceling vanilla item creation. Updated related documentation. +- `eventful`: renamed NEW_UNIT_ACTIVE event to UNIT_NEW_ACTIVE to match the ``EventManager`` event name - `eventful`: fixed UNIT_NEW_ACTIVE event firing too often -- ``widgets.CycleHotkeyLabel``: allow initial option values to be specified as an index instead of an option value - ``job.removeJob()``: fixes regression in DFHack 0.47.05-r5 where items/buildings associated with the job were not getting disassociated when the job is removed. Now `build-now` can build buildings and `gui/mass-remove` can cancel building deconstruction again -- `eventful`: fix ``eventful.registerReaction`` to correctly pass ``call_native`` argument thus allowing canceling vanilla item creation. Updated related documentation. +- ``widgets.CycleHotkeyLabel``: allow initial option values to be specified as an index instead of an option value ## Misc Improvements - `confirm`: added a confirmation dialog for removing manager orders @@ -50,26 +63,23 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `dfhack-examples-guide`: reduce required stock of dye for "Dye cloth" orders - `dfhack-examples-guide`: fix material conditions for making jugs and pots - `dfhack-examples-guide`: make wooden jugs by default to differentiate them from other stone tools. this allows players to more easily select jugs out with a properly-configured stockpile (i.e. the new ``woodentools`` alias) -- `eventful`: renamed NEW_UNIT_ACTIVE event to UNIT_NEW_ACTIVE to match the ``EventManager`` event name - `quickfort-alias-guide`: new aliases: ``forbidsearch``, ``permitsearch``, and ``togglesearch`` use the `search-plugin` plugin to alter the settings for a filtered list of item types when configuring stockpiles - `quickfort-alias-guide`: new aliases: ``stonetools`` and ``woodentools``. the ``jugs`` alias is deprecated. please use ``stonetools`` instead, which is the same as the old ``jugs`` alias. - `quickfort-alias-guide`: new aliases: ``usablehair``, ``permitusablehair``, and ``forbidusablehair`` alter settings for the types of hair/wool that can be made into cloth: sheep, llama, alpaca, and troll. The ``craftrefuse`` aliases have been altered to use this alias as well. - `quickfort-alias-guide`: new aliases: ``forbidthread``, ``permitthread``, ``forbidadamantinethread``, ``permitadamantinethread``, ``forbidcloth``, ``permitcloth``, ``forbidadamantinecloth``, and ``permitadamantinecloth`` give you more control how adamantine-derived items are stored - `quickfort`: `Dreamfort ` blueprint set improvements: automatically create tavern, library, and temple locations (restricted to residents only by default), automatically associate the rented rooms with the tavern -- `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including a werebeast-proof hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. - -## Documentation +- `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including were-bitten hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. ## API - ``word_wrap``: argument ``bool collapse_whitespace`` converted to enum ``word_wrap_whitespace_mode mode``, with valid modes ``WSMODE_KEEP_ALL``, ``WSMODE_COLLAPSE_ALL``, and ``WSMODE_TRIM_LEADING``. ## Lua - ``gui.View``: all ``View`` subclasses (including all ``Widgets``) can now acquire keyboard focus with the new ``View:setFocus()`` function. See docs for details. -- ``widgets.HotkeyLabel``: the ``key_sep`` string is now configurable +- ``materials.ItemTraitsDialog``: new dialog to edit item traits (where "item" is part of a job or work order or similar). The list of traits is the same as in vanilla work order conditions "``t`` change traits". - ``widgets.EditField``: the ``key_sep`` string is now configurable - ``widgets.EditField``: can now display an optional string label in addition to the activation key - ``widgets.EditField``: views that have an ``EditField`` subview no longer need to manually manage the ``EditField`` activation state and input routing. This is now handled automatically by the new ``gui.View`` keyboard focus subsystem. -- ``materials.ItemTraitsDialog``: new dialog to edit item traits (where "item" is part of a job or work order or similar). The list of traits is the same as in vanilla work order conditions "``t`` change traits". +- ``widgets.HotkeyLabel``: the ``key_sep`` string is now configurable # 0.47.05-r5 diff --git a/library/xml b/library/xml index 5dd972b8b..ef5c180ff 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5dd972b8b5823e46a1b418fe483d4913347a13b1 +Subproject commit ef5c180ff220e53ec6b51e01ca3594a5780b8b4b diff --git a/scripts b/scripts index 4941cacca..592f6bd41 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 4941caccab6dd775d43250e79b8d1a75b8a24b57 +Subproject commit 592f6bd41b4ad90655a09d1939da589e6c421be6 From 0ae8a420800540692916395fbc8b629e6f9a8179 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Mon, 20 Jun 2022 19:38:23 +0100 Subject: [PATCH 131/854] Unfinished modding guide --- docs/changelog.txt | 5 +++ docs/guides/modding-guide.rst | 74 +++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 docs/guides/modding-guide.rst diff --git a/docs/changelog.txt b/docs/changelog.txt index e91a427a9..e944b7799 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -69,6 +69,11 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `quickfort-alias-guide`: new aliases: ``forbidthread``, ``permitthread``, ``forbidadamantinethread``, ``permitadamantinethread``, ``forbidcloth``, ``permitcloth``, ``forbidadamantinecloth``, and ``permitadamantinecloth`` give you more control how adamantine-derived items are stored - `quickfort`: `Dreamfort ` blueprint set improvements: automatically create tavern, library, and temple locations (restricted to residents only by default), automatically associate the rented rooms with the tavern - `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including were-bitten hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. +- `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including a werebeast-proof hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. + +## Documentation +- Added modding guide +- Added anchor for DF data structure wrapper in Lua API ## API - ``word_wrap``: argument ``bool collapse_whitespace`` converted to enum ``word_wrap_whitespace_mode mode``, with valid modes ``WSMODE_KEEP_ALL``, ``WSMODE_COLLAPSE_ALL``, and ``WSMODE_TRIM_LEADING``. diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst new file mode 100644 index 000000000..d2c453829 --- /dev/null +++ b/docs/guides/modding-guide.rst @@ -0,0 +1,74 @@ +DFHack modding guide +==================== + +Preface +------- + +Why not just use raw modding? + +It's much less hacky for many things, easier to maintain, and easier to extend. Of course if you're adding a new creature or whatever do use raw modding, but for things like adding syndromes when certain actions are performed, absolutely try out DFHack. Many things will require a mix of raw modding and DFHack. + +What kind of things can we make? + +Lots of things, and the list grows as more and more hooks and tools are developed within DFHack. You can modify behaviours by cleverly watching and modifying certain areas of the game with functions that run on every tick or by using hooks. Familiarising yourself with the many structs oof the game will help with ideas immensely, and you can always ask for help in the right places (e.g. DFHack's Discord). + +You will need to know Lua for the scripting guides herein. + +A script-maker's environment +---------------------------- + +A script is run by writing its path and name from a script path folder without the file extension. E.g. gui/gm-editor for hack/scripts/gui/gm-editor.lua. You can make all your scripts in hack/scripts/, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to dfhack-config/script-paths.txt. You should also make a folder for external installed scripts from the internet that are not in hack/scripts/. + +The structure of the game +------------------------- + +"The game" is in the global variable `df `. The game's memory can be found in ``df.global``, containing things like the list of all items, whether to reindex pathfinding, et cetera. Also relevant to us in ``df`` are the various types found in the game, e.g. ``df.pronoun_type`` which we will be using. + +A script that prints data from a unit +------------------------------------- + +So! It's time to write your first script. We are going to make a script that will get the pronoun type of the currently selected unit (there are many contexts where the function that gets the currently selected unit works). + +First line, we get the unit.:: + + local unit = dfhack.gui.getSelectedUnit() + +If no unit is selected, an error message will be printed (which can be silenced by passing ``true`` to ``getSelectedUnit``) and ``unit`` will be ``nil``. + +If ``unit`` is ``nil``, we don't want the script to run anymore.:: + + if not unit then + return + end + +Now, the field ``sex`` in a unit is an integer, but each integer corresponds to a string value (it, she, or he). We get this value by indexing the bidirectional map ``df.pronoun_type`` with an integer from the unit. Indexing the other way, with one of the strings, will yield its corresponding number. So::: + + local pronounTypeString = df.pronoun_type[unit.sex] + print(pronounTypeString) + +Simple. + +Getting used to gm-editor and DFStructs exploration +--------------------------------------------------- + +So how could you have known about the field and type we just used? + +A script that prints more complex data from a unit +-------------------------------------------------- + +s + +Something that uses eventful and/or repeat-util +----------------------------------------------- + +s + +Setting up an environment for a more advanced modular mod +--------------------------------------------------------- + +s + +A whole mod with multiple hooks and multiple functions that happen on tick +-------------------------------------------------------------------------- + +s From 063e4897a84b8579bfcfd022f02602dff26ddb62 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Mon, 20 Jun 2022 20:31:19 +0100 Subject: [PATCH 132/854] Forgot to stage a change --- docs/Lua API.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 55a217757..f2f252c4b 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -30,6 +30,7 @@ implemented by Lua files located in :file:`hack/lua/*` :local: :depth: 2 +.. _lua-df: ========================= DF data structure wrapper From f33c9bc8813a8588c3288304f271cddb295d0e9d Mon Sep 17 00:00:00 2001 From: quarque2 <107313885+quarque2@users.noreply.github.com> Date: Mon, 20 Jun 2022 21:38:37 +0200 Subject: [PATCH 133/854] Update tile-material.lua (#2218) * Update tile-material.lua * Update changelog.txt * Update changelog.txt * Update changelog.txt * Update tile-material.lua --- docs/changelog.txt | 1 + library/lua/tile-material.lua | 154 +++++++++++++++------------------- 2 files changed, 69 insertions(+), 86 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index e91a427a9..35f344adb 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -46,6 +46,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API ## Lua +- ``tile-material``: fix the order of declarations. The ``GetTileMat`` function now returns the material as intended (always returned nil before). Also changed the license info, with permission of the original author. # 0.47.05-r6 diff --git a/library/lua/tile-material.lua b/library/lua/tile-material.lua index 0e5565d09..ca8b25030 100644 --- a/library/lua/tile-material.lua +++ b/library/lua/tile-material.lua @@ -1,25 +1,7 @@ -- tile-material: Functions to help retrieve the material for a tile. --[[ -Copyright 2015-2016 Milo Christiansen - -This software is provided 'as-is', without any express or implied warranty. In -no event will the authors be held liable for any damages arising from the use of -this software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim -that you wrote the original software. If you use this software in a product, an -acknowledgment in the product documentation would be appreciated but is not -required. - -2. Altered source versions must be plainly marked as such, and must not be -misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. +Original code provided by Milo Christiansen in 2015 under the MIT license. Relicensed under the ZLib license to align with the rest of DFHack, with his permission. ]] local _ENV = mkmodule("tile-material") @@ -85,73 +67,6 @@ local function fixedMat(id) end end --- BasicMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular --- matspec table covers the common case of returning plant materials for plant tiles and other --- materials for the remaining tiles. -BasicMats = { - [df.tiletype_material.AIR] = nil, -- Empty - [df.tiletype_material.SOIL] = GetLayerMat, - [df.tiletype_material.STONE] = GetLayerMat, - [df.tiletype_material.FEATURE] = GetFeatureMat, - [df.tiletype_material.LAVA_STONE] = GetLavaStone, - [df.tiletype_material.MINERAL] = GetVeinMat, - [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), - [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, - [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, - [df.tiletype_material.GRASS_DARK] = GetGrassMat, - [df.tiletype_material.GRASS_DRY] = GetGrassMat, - [df.tiletype_material.GRASS_DEAD] = GetGrassMat, - [df.tiletype_material.PLANT] = GetShrubMat, - [df.tiletype_material.HFS] = nil, -- Eerie Glowing Pit - [df.tiletype_material.CAMPFIRE] = GetLayerMat, - [df.tiletype_material.FIRE] = GetLayerMat, - [df.tiletype_material.ASHES] = GetLayerMat, - [df.tiletype_material.MAGMA] = nil, -- SMR - [df.tiletype_material.DRIFTWOOD] = GetLayerMat, - [df.tiletype_material.POOL] = GetLayerMat, - [df.tiletype_material.BROOK] = GetLayerMat, - [df.tiletype_material.ROOT] = GetLayerMat, - [df.tiletype_material.TREE] = GetTreeMat, - [df.tiletype_material.MUSHROOM] = GetTreeMat, - [df.tiletype_material.UNDERWORLD_GATE] = nil, -- I guess this is for the gates found in vaults? -} - --- NoPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular --- matspec table will ignore plants, returning layer materials (or nil for trees) instead. -NoPlantMats = { - [df.tiletype_material.SOIL] = GetLayerMat, - [df.tiletype_material.STONE] = GetLayerMat, - [df.tiletype_material.FEATURE] = GetFeatureMat, - [df.tiletype_material.LAVA_STONE] = GetLavaStone, - [df.tiletype_material.MINERAL] = GetVeinMat, - [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), - [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, - [df.tiletype_material.GRASS_LIGHT] = GetLayerMat, - [df.tiletype_material.GRASS_DARK] = GetLayerMat, - [df.tiletype_material.GRASS_DRY] = GetLayerMat, - [df.tiletype_material.GRASS_DEAD] = GetLayerMat, - [df.tiletype_material.PLANT] = GetLayerMat, - [df.tiletype_material.CAMPFIRE] = GetLayerMat, - [df.tiletype_material.FIRE] = GetLayerMat, - [df.tiletype_material.ASHES] = GetLayerMat, - [df.tiletype_material.DRIFTWOOD] = GetLayerMat, - [df.tiletype_material.POOL] = GetLayerMat, - [df.tiletype_material.BROOK] = GetLayerMat, - [df.tiletype_material.ROOT] = GetLayerMat, -} - --- OnlyPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular --- matspec table will return nil for any non-plant tile. Plant tiles return the plant material. -OnlyPlantMats = { - [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, - [df.tiletype_material.GRASS_DARK] = GetGrassMat, - [df.tiletype_material.GRASS_DRY] = GetGrassMat, - [df.tiletype_material.GRASS_DEAD] = GetGrassMat, - [df.tiletype_material.PLANT] = GetShrubMat, - [df.tiletype_material.TREE] = GetTreeMat, - [df.tiletype_material.MUSHROOM] = GetTreeMat, -} - -- GetLayerMat returns the layer material for the given tile. -- AFAIK this will never return nil. function GetLayerMat(x, y, z) @@ -349,6 +264,73 @@ function GetFeatureMat(x, y, z) return nil end +-- BasicMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table covers the common case of returning plant materials for plant tiles and other +-- materials for the remaining tiles. +BasicMats = { + [df.tiletype_material.AIR] = nil, -- Empty + [df.tiletype_material.SOIL] = GetLayerMat, + [df.tiletype_material.STONE] = GetLayerMat, + [df.tiletype_material.FEATURE] = GetFeatureMat, + [df.tiletype_material.LAVA_STONE] = GetLavaStone, + [df.tiletype_material.MINERAL] = GetVeinMat, + [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), + [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, + [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, + [df.tiletype_material.GRASS_DARK] = GetGrassMat, + [df.tiletype_material.GRASS_DRY] = GetGrassMat, + [df.tiletype_material.GRASS_DEAD] = GetGrassMat, + [df.tiletype_material.PLANT] = GetShrubMat, + [df.tiletype_material.HFS] = nil, -- Eerie Glowing Pit + [df.tiletype_material.CAMPFIRE] = GetLayerMat, + [df.tiletype_material.FIRE] = GetLayerMat, + [df.tiletype_material.ASHES] = GetLayerMat, + [df.tiletype_material.MAGMA] = nil, -- SMR + [df.tiletype_material.DRIFTWOOD] = GetLayerMat, + [df.tiletype_material.POOL] = GetLayerMat, + [df.tiletype_material.BROOK] = GetLayerMat, + [df.tiletype_material.ROOT] = GetLayerMat, + [df.tiletype_material.TREE] = GetTreeMat, + [df.tiletype_material.MUSHROOM] = GetTreeMat, + [df.tiletype_material.UNDERWORLD_GATE] = nil, -- I guess this is for the gates found in vaults? +} + +-- NoPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table will ignore plants, returning layer materials (or nil for trees) instead. +NoPlantMats = { + [df.tiletype_material.SOIL] = GetLayerMat, + [df.tiletype_material.STONE] = GetLayerMat, + [df.tiletype_material.FEATURE] = GetFeatureMat, + [df.tiletype_material.LAVA_STONE] = GetLavaStone, + [df.tiletype_material.MINERAL] = GetVeinMat, + [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), + [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, + [df.tiletype_material.GRASS_LIGHT] = GetLayerMat, + [df.tiletype_material.GRASS_DARK] = GetLayerMat, + [df.tiletype_material.GRASS_DRY] = GetLayerMat, + [df.tiletype_material.GRASS_DEAD] = GetLayerMat, + [df.tiletype_material.PLANT] = GetLayerMat, + [df.tiletype_material.CAMPFIRE] = GetLayerMat, + [df.tiletype_material.FIRE] = GetLayerMat, + [df.tiletype_material.ASHES] = GetLayerMat, + [df.tiletype_material.DRIFTWOOD] = GetLayerMat, + [df.tiletype_material.POOL] = GetLayerMat, + [df.tiletype_material.BROOK] = GetLayerMat, + [df.tiletype_material.ROOT] = GetLayerMat, +} + +-- OnlyPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table will return nil for any non-plant tile. Plant tiles return the plant material. +OnlyPlantMats = { + [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, + [df.tiletype_material.GRASS_DARK] = GetGrassMat, + [df.tiletype_material.GRASS_DRY] = GetGrassMat, + [df.tiletype_material.GRASS_DEAD] = GetGrassMat, + [df.tiletype_material.PLANT] = GetShrubMat, + [df.tiletype_material.TREE] = GetTreeMat, + [df.tiletype_material.MUSHROOM] = GetTreeMat, +} + -- GetTileMat will return the material of the specified tile as determined by its tile type and the -- world geology data, etc. -- The returned material should exactly match the material reported by DF except in cases where is From a880a2b92d5cb8e7c3f15d7c80315cee736b24c6 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:48:28 +0100 Subject: [PATCH 134/854] Move changelog entries to proper place --- docs/changelog.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index e944b7799..a07335715 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements ## Documentation +- Added modding guide +- Added anchor for DF data structure wrapper in Lua API ## API @@ -72,8 +74,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including a werebeast-proof hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. ## Documentation -- Added modding guide -- Added anchor for DF data structure wrapper in Lua API ## API - ``word_wrap``: argument ``bool collapse_whitespace`` converted to enum ``word_wrap_whitespace_mode mode``, with valid modes ``WSMODE_KEEP_ALL``, ``WSMODE_COLLAPSE_ALL``, and ``WSMODE_TRIM_LEADING``. From ccee6ba48700c16af900e7ff0a5cec54e1cfa0c4 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:53:59 +0100 Subject: [PATCH 135/854] Added link to modding guide from changelog --- docs/changelog.txt | 2 +- docs/guides/modding-guide.rst | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a07335715..2118a5380 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,7 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements ## Documentation -- Added modding guide +- Added `modding guide ` - Added anchor for DF data structure wrapper in Lua API ## API diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index d2c453829..5d55ca44e 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -1,3 +1,5 @@ +.. _modding-guide: + DFHack modding guide ==================== From 4c2a533de0960a8fc27d0105c56a550b6ffb0fdb Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:54:28 +0100 Subject: [PATCH 136/854] Remove "added anchor" from changelog --- docs/changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 2118a5380..a54957796 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -43,7 +43,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Documentation - Added `modding guide ` -- Added anchor for DF data structure wrapper in Lua API ## API From 88a9755d785e2ecd751c7e0ee1711aadf188eb85 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:54:45 +0100 Subject: [PATCH 137/854] Update docs/guides/modding-guide.rst Co-authored-by: Myk --- docs/guides/modding-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 5d55ca44e..58f32fa28 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -26,7 +26,7 @@ The structure of the game "The game" is in the global variable `df `. The game's memory can be found in ``df.global``, containing things like the list of all items, whether to reindex pathfinding, et cetera. Also relevant to us in ``df`` are the various types found in the game, e.g. ``df.pronoun_type`` which we will be using. -A script that prints data from a unit +Your first script ------------------------------------- So! It's time to write your first script. We are going to make a script that will get the pronoun type of the currently selected unit (there are many contexts where the function that gets the currently selected unit works). From 92ab6b10529509b804bae8a4d86397da1cade856 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:58:29 +0100 Subject: [PATCH 138/854] Update docs/guides/modding-guide.rst Co-authored-by: Myk --- docs/guides/modding-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 58f32fa28..dfd143e08 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -14,7 +14,7 @@ What kind of things can we make? Lots of things, and the list grows as more and more hooks and tools are developed within DFHack. You can modify behaviours by cleverly watching and modifying certain areas of the game with functions that run on every tick or by using hooks. Familiarising yourself with the many structs oof the game will help with ideas immensely, and you can always ask for help in the right places (e.g. DFHack's Discord). -You will need to know Lua for the scripting guides herein. +DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at . A script-maker's environment ---------------------------- From 52161df42863a14c18c43a95a90495eaf7a2811d Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:58:36 +0100 Subject: [PATCH 139/854] Update docs/guides/modding-guide.rst Co-authored-by: Myk --- docs/guides/modding-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index dfd143e08..d4514156e 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -16,7 +16,7 @@ Lots of things, and the list grows as more and more hooks and tools are develope DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at . -A script-maker's environment +A mod-maker's development environment ---------------------------- A script is run by writing its path and name from a script path folder without the file extension. E.g. gui/gm-editor for hack/scripts/gui/gm-editor.lua. You can make all your scripts in hack/scripts/, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to dfhack-config/script-paths.txt. You should also make a folder for external installed scripts from the internet that are not in hack/scripts/. From 6f2998d7e5514f4a7bbd922f33dae4efdc33c901 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:58:40 +0100 Subject: [PATCH 140/854] Update docs/guides/modding-guide.rst Co-authored-by: Myk --- docs/guides/modding-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index d4514156e..c8f13baf7 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -10,7 +10,7 @@ Why not just use raw modding? It's much less hacky for many things, easier to maintain, and easier to extend. Of course if you're adding a new creature or whatever do use raw modding, but for things like adding syndromes when certain actions are performed, absolutely try out DFHack. Many things will require a mix of raw modding and DFHack. -What kind of things can we make? +What kind of mods can we make? Lots of things, and the list grows as more and more hooks and tools are developed within DFHack. You can modify behaviours by cleverly watching and modifying certain areas of the game with functions that run on every tick or by using hooks. Familiarising yourself with the many structs oof the game will help with ideas immensely, and you can always ask for help in the right places (e.g. DFHack's Discord). From 41526f376d9bc9bcb68b6526eb4f31e72bb6003e Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 13:02:10 +0100 Subject: [PATCH 141/854] Fix wrong - length --- docs/guides/modding-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index c8f13baf7..e60bc2650 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -17,7 +17,7 @@ Lots of things, and the list grows as more and more hooks and tools are develope DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at . A mod-maker's development environment ----------------------------- +------------------------------------- A script is run by writing its path and name from a script path folder without the file extension. E.g. gui/gm-editor for hack/scripts/gui/gm-editor.lua. You can make all your scripts in hack/scripts/, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to dfhack-config/script-paths.txt. You should also make a folder for external installed scripts from the internet that are not in hack/scripts/. @@ -27,7 +27,7 @@ The structure of the game "The game" is in the global variable `df `. The game's memory can be found in ``df.global``, containing things like the list of all items, whether to reindex pathfinding, et cetera. Also relevant to us in ``df`` are the various types found in the game, e.g. ``df.pronoun_type`` which we will be using. Your first script -------------------------------------- +----------------- So! It's time to write your first script. We are going to make a script that will get the pronoun type of the currently selected unit (there are many contexts where the function that gets the currently selected unit works). From a9ab415ac7f5443a7129db461055125a7d808f6d Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 15:20:49 +0100 Subject: [PATCH 142/854] Clean merge heckery and clean up label reference --- docs/changelog.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a54957796..797a79f3a 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,7 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements ## Documentation -- Added `modding guide ` +- Added `modding-guide` ## API @@ -70,9 +70,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `quickfort-alias-guide`: new aliases: ``forbidthread``, ``permitthread``, ``forbidadamantinethread``, ``permitadamantinethread``, ``forbidcloth``, ``permitcloth``, ``forbidadamantinecloth``, and ``permitadamantinecloth`` give you more control how adamantine-derived items are stored - `quickfort`: `Dreamfort ` blueprint set improvements: automatically create tavern, library, and temple locations (restricted to residents only by default), automatically associate the rented rooms with the tavern - `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including were-bitten hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. -- `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including a werebeast-proof hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. - -## Documentation ## API - ``word_wrap``: argument ``bool collapse_whitespace`` converted to enum ``word_wrap_whitespace_mode mode``, with valid modes ``WSMODE_KEEP_ALL``, ``WSMODE_COLLAPSE_ALL``, and ``WSMODE_TRIM_LEADING``. From 9163728b99f40bd5b89577d29d735eaa20fe0def Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 21 Jun 2022 12:09:12 -0700 Subject: [PATCH 143/854] hide blueprints that should be hidden, update help --- data/blueprints/library/dreamfort.csv | 57 +++++++++++++-------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index 296e6c88b..a00a00e9e 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -74,12 +74,12 @@ quickfort run library/dreamfort.csv -n /farming3,# Run when furniture has been p quickfort run library/dreamfort.csv -n /industry2,# Run when the industry level has been dug out. prioritize ConstructBuilding,# To get those workshops up and running ASAP. You may have to run this several times as the materials for the building construction jobs become ready. quickfort run library/dreamfort.csv -n /surface4,"# Run after the walls and floors are built on the surface. Even if /surface3 is finished before you run /industry2, though, wait until after /industry2 to run this blueprint so that surface walls, floors, and roofing don't prevent your workshops from being built (due to lack of blocks)." -"quickfort run,orders library/dreamfort.csv -n /services2",# Run when the services levels have been dug out. Feel free to remove the orders for the ropes if you already brought them with you. +"quickfort orders,run library/dreamfort.csv -n /services2",# Run when the services levels have been dug out. Feel free to remove the orders for the ropes if you already brought them with you. orders import basic,"# Run after the first migration wave, so you have dorfs to do all the basic tasks. Note that this is the ""orders"" plugin, not the ""quickfort orders"" command." -"quickfort run,orders library/dreamfort.csv -n /surface5","# Run when all marked trees on the surface are chopped down and walls and floors have been constructed, including the roof section over the future barracks." +"quickfort orders,run library/dreamfort.csv -n /surface5","# Run when all marked trees on the surface are chopped down and walls and floors have been constructed, including the roof section over the future barracks." prioritize ConstructBuilding,# Run when you see the bridges ready to be built so the busy masons come and build them. -"quickfort run,orders library/dreamfort.csv -n /surface6",# Run when at least the beehives and weapon rack are constructed and you have linked all levers to their respective bridges. -"quickfort run,orders library/dreamfort.csv -n /surface7",# Run after the surface walls are completed and any marked trees are chopped down. +"quickfort orders,run library/dreamfort.csv -n /surface6",# Run when at least the beehives and weapon rack are constructed and you have linked all levers to their respective bridges. +"quickfort orders,run library/dreamfort.csv -n /surface7",# Run after the surface walls are completed and any marked trees are chopped down. "" -- Plumbing -- "This is a good time to fill your well cisterns, either with a bucket brigade or by routing water from a freshwater stream or an aquifer (see the library/aquifer_tap.csv blueprint for help with this)." @@ -87,23 +87,23 @@ prioritize ConstructBuilding,# Run when you see the bridges ready to be built so "" -- Mature fort (third migration wave onward) -- orders import furnace,# Automated production of basic furnace-related items. Don't forget to create a sand collection zone (or remove the sand- and glass-related orders if you have no sand). -"quickfort run,orders library/dreamfort.csv -n /suites2",# Run when the suites level has been dug out. -"quickfort run,orders library/dreamfort.csv -n /surface8","# Run if/when you need longer trap corridors on the surface for larger sieges, anytime after you run /surface7." -"quickfort run,orders library/dreamfort.csv -n /apartments2",# Run when the first apartment level has been dug out. -"quickfort run,orders library/dreamfort.csv -n /services3","# Run after the dining table and chair, weapon rack, and archery targets have been constructed. Also wait until after you complete /surface7, though, because surface defenses are more important than a grand dining hall." -"quickfort run,orders library/dreamfort.csv -n /guildhall2",# Run when the guildhall level has been dug out. -"quickfort run,orders library/dreamfort.csv -n ""/guildhall3, /guildhall4""",# Optionally run after /guildhall2 to build default furnishings and declare a library and temple. -"quickfort run,orders library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed on the first apartments level. -"quickfort run,orders library/dreamfort.csv -n /farming4",# Run once you have a cache of potash. +"quickfort orders,run library/dreamfort.csv -n /suites2",# Run when the suites level has been dug out. +"quickfort orders,run library/dreamfort.csv -n /surface8","# Run if/when you need longer trap corridors on the surface for larger sieges, anytime after you run /surface7." +"quickfort orders,run library/dreamfort.csv -n /apartments2",# Run when the first apartment level has been dug out. +"quickfort orders,run library/dreamfort.csv -n /services3","# Run after the dining table and chair, weapon rack, and archery targets have been constructed. Also wait until after you complete /surface7, though, because surface defenses are more important than a grand dining hall." +"quickfort orders,run library/dreamfort.csv -n /guildhall2",# Run when the guildhall level has been dug out. +"quickfort orders,run library/dreamfort.csv -n ""/guildhall3, /guildhall4""",# Optionally run after /guildhall2 to build default furnishings and declare a library and temple. +"quickfort orders,run library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed on the first apartments level. +"quickfort orders,run library/dreamfort.csv -n /farming4",# Run once you have a cache of potash. orders import military,# Automated production of military equipment. Turn on automelt in the meltables piles on the industry level to automatically upgrade all metal military equipment to masterwork quality. These orders are optional if you are not using a military. orders import smelting,# Automated production of all types of metal bars. -"quickfort run,orders library/dreamfort.csv -n /services4","# Run when you need a jail, anytime after the restraints are placed from /services3." +"quickfort orders,run library/dreamfort.csv -n /services4","# Run when you need a jail, anytime after the restraints are placed from /services3." orders import rockstock,# Maintains a small stock of all types of rock furniture. orders import glassstock,# Maintains a small stock of all types of glass furniture and parts (only import if you have sand). "" -- Repeat for each remaining apartments level as needed -- -"quickfort run,orders library/dreamfort.csv -n /apartments2",# Run when the apartment level has been dug out. -"quickfort run,orders library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed. +"quickfort orders,run library/dreamfort.csv -n /apartments2",# Run when the apartment level has been dug out. +"quickfort orders,run library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed. burial -pets,# Run once the coffins are placed to set them to allow for burial. See this checklist online at https://docs.google.com/spreadsheets/d/13PVZ2h3Mm3x_G1OXQvwKd7oIR2lK4A1Ahf6Om1kFigw/edit#gid=1459509569 @@ -232,6 +232,7 @@ https://drive.google.com/file/d/1Et42JTzeYK23iI5wrPMsFJ7lUXwVBQob/view?usp=shari [PET:2:BIRD_GOOSE:FEMALE:STANDARD] [PET:2:BIRD_GOOSE:MALE:STANDARD] #meta label(all_orders) hidden() references all blueprints that generate orders; for testing only +/surface1 /surface2 /surface3 /surface4 @@ -239,27 +240,25 @@ https://drive.google.com/file/d/1Et42JTzeYK23iI5wrPMsFJ7lUXwVBQob/view?usp=shari /surface6 /surface7 /surface8 +/farming1 /farming2 /farming3 /farming4 +/industry1 /industry2 +/services1 /services2 /services3 /services4 +/guildhall1 /guildhall2 +/guildhall3 +/guildhall4 +/suites1 /suites2 -/apartments2 -/apartments2 -/apartments2 -/apartments2 -/apartments2 -/apartments2 -/apartments3 -/apartments3 -/apartments3 -/apartments3 -/apartments3 -/apartments3 +/apartments1 repeat(>5) +/apartments2 repeat(>5) +/apartments3 repeat(>5) #notes label(surface_help) Sets up a protected entrance to your fort in a flat area on the surface. Screenshot: https://drive.google.com/file/d/1YL_vQJLB2YnUEFrAg9y3HEdFq3Wpw9WP @@ -2382,7 +2381,7 @@ query_jail/services_query_jail ,`,`,`,`,`,,`,`,`,`,` ,`,`,`,`,`,,`,`,`,`,` -"#query label(services_query_dining) start(18; 18) message(The tavern is restricted to residents only by default. If you'd like your tavern to attract vistors, please go to the (l)ocation menu and change the restriction.) set up dining room/tavern and barracks" +"#query label(services_query_dining) start(18; 18) hidden() message(The tavern is restricted to residents only by default. If you'd like your tavern to attract vistors, please go to the (l)ocation menu and change the restriction.) set up dining room/tavern and barracks" ,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` ,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` @@ -2418,7 +2417,7 @@ query_jail/services_query_jail ,`,`,`,`,`,,`,`,`,`,` ,`,`,`,`,`,,`,`,`,`,` -#query label(services_query_rented_rooms) start(18; 18) attach rented rooms to tavern +#query label(services_query_rented_rooms) start(18; 18) hidden() attach rented rooms to tavern ,r&l-&,r&l-&,r&l-&,,r&l-&,r&l-&,r&l-&,,r&l-&,r&l-&,r&l-&,,`,`,`,,`,,`,`,` ,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` From ac864204c2258151b05bbe019c006c26e2c14e6b Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 21:07:35 +0100 Subject: [PATCH 144/854] More work on guide, added to existing sections and revised sections list --- docs/guides/modding-guide.rst | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index e60bc2650..f124ba121 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -8,18 +8,24 @@ Preface Why not just use raw modding? -It's much less hacky for many things, easier to maintain, and easier to extend. Of course if you're adding a new creature or whatever do use raw modding, but for things like adding syndromes when certain actions are performed, absolutely try out DFHack. Many things will require a mix of raw modding and DFHack. +For many things it's either completely and only (sensibly) doable in raws or completely and only doable with DFHack. For mods where DFHack is an alternative and not the only option, it's much less hacky, easier to maintain, and easier to extend, and is not prone to side-effects. A great example is adding a syndrome when a reaction is performed requiring an exploding boulder in raws but having dedicated tools for it if you use DFHack. Many things will require a mix of raw modding and DFHack. What kind of mods can we make? -Lots of things, and the list grows as more and more hooks and tools are developed within DFHack. You can modify behaviours by cleverly watching and modifying certain areas of the game with functions that run on every tick or by using hooks. Familiarising yourself with the many structs oof the game will help with ideas immensely, and you can always ask for help in the right places (e.g. DFHack's Discord). +Lots of things, and the list grows as more and more hooks and tools are developed within DFHack. You can modify behaviours by cleverly watching and modifying certain areas of the game with functions that run on every tick or by using hooks. Familiarising yourself with the many structs of the game will help with ideas immensely, and you can always ask for help in the right places (e.g. DFHack's Discord). -DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at . +Examples of things we have mod tools to allow are directly changing skills, spawning liquids, adding new powered buildings, creating items/trees/units based on various conditions (in reactions or similar), running code when certain projectiles hit or move, and much else besides. + +DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at https://www.lua.org/pil/1.html. A mod-maker's development environment ------------------------------------- -A script is run by writing its path and name from a script path folder without the file extension. E.g. gui/gm-editor for hack/scripts/gui/gm-editor.lua. You can make all your scripts in hack/scripts/, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to dfhack-config/script-paths.txt. You should also make a folder for external installed scripts from the internet that are not in hack/scripts/. +Scripts can be run from a world's ``raw/scripts/`` directory, and (configurably) are run by default from ``hack/scripts/``. Scripts in ``raw/init.d/`` are automatically run on world load. Scripts within the raws are a component for more advanced mods. + +A script is run by writing its path and name from a script path folder without the file extension into a DFHack command prompt (in-game or the external one). E.g. ``gui/gm-editor`` for ``hack/scripts/gui/gm-editor.lua``. + +You can make all your scripts in ``hack/scripts/``, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to ``dfhack-config/script-paths.txt``. You should also make a folder for external installed scripts from the internet that are not in ``hack/scripts/``. You can prepend your script paths entries with a ``+`` so that they take precedence over other folders. The structure of the game ------------------------- @@ -48,20 +54,17 @@ Now, the field ``sex`` in a unit is an integer, but each integer corresponds to local pronounTypeString = df.pronoun_type[unit.sex] print(pronounTypeString) -Simple. +Simple. Save this as a Lua file in your own scripts directory and run it as shown before when focused on a unit one way or another. Getting used to gm-editor and DFStructs exploration --------------------------------------------------- So how could you have known about the field and type we just used? -A script that prints more complex data from a unit --------------------------------------------------- - s -Something that uses eventful and/or repeat-util ------------------------------------------------ +Detecting triggers +------------------ s @@ -70,7 +73,7 @@ Setting up an environment for a more advanced modular mod s -A whole mod with multiple hooks and multiple functions that happen on tick --------------------------------------------------------------------------- +Your first whole mod +-------------------- s From f812e09ae1470b26d3375337bc98de90f27692b4 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 21:35:07 +0100 Subject: [PATCH 145/854] Update modding guide with an extra paragraph and syntax fixes --- docs/guides/modding-guide.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index f124ba121..d3bf384c8 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -27,6 +27,8 @@ A script is run by writing its path and name from a script path folder without t You can make all your scripts in ``hack/scripts/``, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to ``dfhack-config/script-paths.txt``. You should also make a folder for external installed scripts from the internet that are not in ``hack/scripts/``. You can prepend your script paths entries with a ``+`` so that they take precedence over other folders. +If your mod is installed into ``raw/scripts/`` be aware that the copies of the scripts in ``data/save/*/raw/`` are checked first and will run instead of any changes you make to an in-development copy outside of a raw folder. + The structure of the game ------------------------- @@ -37,19 +39,19 @@ Your first script So! It's time to write your first script. We are going to make a script that will get the pronoun type of the currently selected unit (there are many contexts where the function that gets the currently selected unit works). -First line, we get the unit.:: +First line, we get the unit. :: local unit = dfhack.gui.getSelectedUnit() If no unit is selected, an error message will be printed (which can be silenced by passing ``true`` to ``getSelectedUnit``) and ``unit`` will be ``nil``. -If ``unit`` is ``nil``, we don't want the script to run anymore.:: +If ``unit`` is ``nil``, we don't want the script to run anymore. :: if not unit then return end -Now, the field ``sex`` in a unit is an integer, but each integer corresponds to a string value (it, she, or he). We get this value by indexing the bidirectional map ``df.pronoun_type`` with an integer from the unit. Indexing the other way, with one of the strings, will yield its corresponding number. So::: +Now, the field ``sex`` in a unit is an integer, but each integer corresponds to a string value (it, she, or he). We get this value by indexing the bidirectional map ``df.pronoun_type`` with an integer from the unit. Indexing the other way, with one of the strings, will yield its corresponding number. So: :: local pronounTypeString = df.pronoun_type[unit.sex] print(pronounTypeString) From ba629b8e0a022e8891d7177ebc58f53cc923244b Mon Sep 17 00:00:00 2001 From: Myk Date: Tue, 21 Jun 2022 16:38:04 -0700 Subject: [PATCH 146/854] manually handle DestroyBuilding jobs (#2209) * don't delete general refs from jobs that we cancel though we still disconnect the refs if we can * get job remove working in all cases we apparently need to manually handle DestroyBuilding jobs everything else we should let cancel_job handle * update changelog --- docs/changelog.txt | 1 + library/modules/Job.cpp | 31 ++++++++++++++----------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 35f344adb..6499a1fb5 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -38,6 +38,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## New Tweaks ## Fixes +- ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled ## Misc Improvements diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 749be199e..ff158caa0 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -360,27 +360,24 @@ bool DFHack::Job::removeJob(df::job* job) { using df::global::world; CHECK_NULL_POINTER(job); - // cancel_job below does not clean up refs, so we have to do that first + // cancel_job below does not clean up all refs, so we have to do some work - // clean up general refs - for (auto genRef : job->general_refs) { - if (!genRef) continue; - - // disconnectJobGeneralRef only handles buildings and units - if (genRef->getType() != general_ref_type::BUILDING_HOLDER && - genRef->getType() != general_ref_type::UNIT_WORKER) - return false; - } + // manually handle DESTROY_BUILDING jobs (cancel_job doesn't handle them) + if (job->job_type == df::job_type::DestroyBuilding) { + for (auto &genRef : job->general_refs) { + disconnectJobGeneralRef(job, genRef); + if (genRef) delete genRef; + } + job->general_refs.resize(0); - for (auto genRef : job->general_refs) { - // this should always succeed because of the check in the preceding loop - bool success = disconnectJobGeneralRef(job, genRef); - assert(success); (void)success; - if (genRef) delete genRef; + // remove the job from the world + job->list_link->prev->next = job->list_link->next; + delete job->list_link; + delete job; + return true; } - job->general_refs.resize(0); - // clean up item refs + // clean up item refs and delete them for (auto &item_ref : job->items) { disconnectJobItem(job, item_ref); if (item_ref) delete item_ref; From 234fcb8fe32f84aadfefc13266020c276f9e70e0 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 22 Jun 2022 07:17:32 +0000 Subject: [PATCH 147/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 592f6bd41..90abe4a64 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 592f6bd41b4ad90655a09d1939da589e6c421be6 +Subproject commit 90abe4a64d1babea491a113b487df73aecc1e3ff From 280737365687a810da7d4d465e2054ed472624ad Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 22 Jun 2022 19:02:58 +0000 Subject: [PATCH 148/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 90abe4a64..dab8b2246 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 90abe4a64d1babea491a113b487df73aecc1e3ff +Subproject commit dab8b22464cc81c7bc59e3e0f19ea3999676ffdf From b0bff47f0377f8845425e92b9c42ddde056e712c Mon Sep 17 00:00:00 2001 From: Simon Lees Date: Thu, 23 Jun 2022 22:19:05 +0930 Subject: [PATCH 149/854] Fix use after free's This was detected by gcc and causing the build to fail on my Linux machine --- plugins/orders.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/orders.cpp b/plugins/orders.cpp index c984f5060..85ba62486 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -540,10 +540,10 @@ static command_result orders_import(color_ostream &out, Json::Value &orders) } else { - delete order; - out << COLOR_LIGHTRED << "Invalid item subtype for imported manager order: " << enum_item_key(order->item_type) << ":" << it["item_subtype"].asString() << std::endl; + delete order; + return CR_FAILURE; } } @@ -716,10 +716,10 @@ static command_result orders_import(color_ostream &out, Json::Value &orders) } else { - delete condition; - out << COLOR_YELLOW << "Invalid item condition item subtype for imported manager order: " << enum_item_key(condition->item_type) << ":" << it2["item_subtype"].asString() << std::endl; + delete condition; + continue; } } From 9788a8a22a344e0b1c50cde410b7c57ebad44496 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Wed, 29 Jun 2022 01:27:18 +0200 Subject: [PATCH 150/854] Add default selection handler to `materials.ItemTraitsDialog` (#2211) * add forward compatibility for future `job_item_flags` * add default selection handler to `materials.ItemTraitsDialog` * add a call to `error()` in 'unknown'-branch inside `setTrait` * add `ItemTraitsDialog` improvement description to changelog.txt --- docs/changelog.txt | 2 + library/lua/gui/materials.lua | 96 ++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 6499a1fb5..2d5a5cf64 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements +- ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. + ## Documentation ## API diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index c9eaaf38d..c81161bc5 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -345,7 +345,8 @@ end function ItemTraitsDialog(args) local job_item_flags_map = {} - for i = 1, 3 do + for i = 1, 5 do + if not df['job_item_flags'..i] then break end for _, f in ipairs(df['job_item_flags'..i]) do if f then job_item_flags_map[f] = 'flags'..i @@ -600,6 +601,99 @@ function ItemTraitsDialog(args) args.on_select = function(idx, obj) return cb(obj) end + else + local function toggleFlag(obj, ffield, flag) + local job_item = obj.job_item + job_item[ffield][flag] = not job_item[ffield][flag] + end + + local function toggleToolUse(obj, tool_use) + local job_item = obj.job_item + tool_use = df.tool_uses[tool_use] + if job_item.has_tool_use == tool_use then + job_item.has_tool_use = df.tool_uses.NONE + else + job_item.has_tool_use = tool_use + end + end + + local function toggleMetalOre(obj, ore_ix) + local job_item = obj.job_item + if job_item.metal_ore == ore_ix then + job_item.metal_ore = -1 + else + job_item.metal_ore = ore_ix + end + end + + function toggleReactionClass(obj, reaction_class) + local job_item = obj.job_item + if job_item.reaction_class == reaction_class then + job_item.reaction_class = '' + else + job_item.reaction_class = reaction_class + end + end + + local function toggleProductMaterial(obj, product_materials) + local job_item = obj.job_item + if job_item.has_material_reaction_product == product_materials then + job_item.has_material_reaction_product = '' + else + job_item.has_material_reaction_product = product_materials + end + end + + local function unsetFlags(obj) + local job_item = obj.job_item + for flag, ffield in pairs(job_item_flags_map) do + if job_item[ffield][flag] then + toggleFlag(obj, ffield, flag) + end + end + end + + local function setTrait(obj, sel) + if sel.ffield then + --print('toggle flag', sel.ffield, sel.flag) + toggleFlag(obj, sel.ffield, sel.flag) + elseif sel.reset_flags then + --print('reset every flag') + unsetFlags(obj) + elseif sel.tool_use then + --print('toggle tool_use', sel.tool_use) + toggleToolUse(obj, sel.tool_use) + elseif sel.ore_ix then + --print('toggle ore', sel.ore_ix) + toggleMetalOre(obj, sel.ore_ix) + elseif sel.reaction_class then + --print('toggle reaction class', sel.reaction_class) + toggleReactionClass(obj, sel.reaction_class) + elseif sel.product_materials then + --print('toggle product materials', sel.product_materials) + toggleProductMaterial(obj, sel.product_materials) + elseif sel.reset_all_traits then + --print('reset every trait') + -- flags + unsetFlags(obj) + -- tool use + toggleToolUse(obj, 'NONE') + -- metal ore + toggleMetalOre(obj, -1) + -- reaction class + toggleReactionClass(obj, '') + -- producing + toggleProductMaterial(obj, '') + else + print('unknown sel') + printall(sel) + error('Selected entry in ItemTraitsDialog was of unknown type') + end + end + + args.on_select = function(idx, choice) + setTrait(args, choice) + end end return dlg.ListBox(args) From 12958e15c6ae80e88191ccf40cc7e0c07e83f546 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 29 Jun 2022 07:17:24 +0000 Subject: [PATCH 151/854] Auto-update submodules plugins/stonesense: master --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 841803ca6..8ddba1944 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 841803ca6f173f4d97eea376b4dcd512336e35e2 +Subproject commit 8ddba1944274d0f22fef4bc6cc52229818e580bb From 8a605e190305f80603a99ae77860e080cb9e5626 Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 29 Jun 2022 08:27:37 -0600 Subject: [PATCH 152/854] The great de-anon-ification --- library/xml | 2 +- plugins/building-hacks.cpp | 12 ++++++------ plugins/orders.cpp | 8 ++++---- scripts | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/library/xml b/library/xml index ef5c180ff..e803099f5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ef5c180ff220e53ec6b51e01ca3594a5780b8b4b +Subproject commit e803099f5d983055e4b5b00a6baf4bcc91c6f25d diff --git a/plugins/building-hacks.cpp b/plugins/building-hacks.cpp index 2c75d4622..635d99261 100644 --- a/plugins/building-hacks.cpp +++ b/plugins/building-hacks.cpp @@ -88,8 +88,8 @@ struct work_hook : df::building_workshopst{ df::general_ref_creaturest* ref = static_cast(DFHack::Buildings::getGeneralRef(this, general_ref_type::CREATURE)); if (ref) { - info->produced = ref->anon_1; - info->consumed = ref->anon_2; + info->produced = ref->unk_1; + info->consumed = ref->unk_2; return true; } else @@ -118,14 +118,14 @@ struct work_hook : df::building_workshopst{ df::general_ref_creaturest* ref = static_cast(DFHack::Buildings::getGeneralRef(this, general_ref_type::CREATURE)); if (ref) { - ref->anon_1 = produced; - ref->anon_2 = consumed; + ref->unk_1 = produced; + ref->unk_2 = consumed; } else { ref = df::allocate(); - ref->anon_1 = produced; - ref->anon_2 = consumed; + ref->unk_1 = produced; + ref->unk_2 = consumed; general_refs.push_back(ref); } } diff --git a/plugins/orders.cpp b/plugins/orders.cpp index 85ba62486..fb678ca6d 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -467,7 +467,7 @@ static command_result orders_export_command(color_ostream & out, const std::stri condition["order"] = it2->order_id; condition["condition"] = enum_item_key(it2->condition); - // TODO: anon_1 + // TODO: unk_1 conditions.append(condition); } @@ -475,7 +475,7 @@ static command_result orders_export_command(color_ostream & out, const std::stri order["order_conditions"] = conditions; } - // TODO: anon_1 + // TODO: items orders.append(order); } @@ -873,13 +873,13 @@ static command_result orders_import(color_ostream &out, Json::Value &orders) continue; } - // TODO: anon_1 + // TODO: unk_1 order->order_conditions.push_back(condition); } } - // TODO: anon_1 + // TODO: items world->manager_orders.push_back(order); } diff --git a/scripts b/scripts index dab8b2246..274b81ded 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit dab8b22464cc81c7bc59e3e0f19ea3999676ffdf +Subproject commit 274b81ded9c72bff23eca4f0c2a78cb6cb900f8e From 0aa7ec877e4201af76b429438037fb5fc8a501c2 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 29 Jun 2022 15:17:13 +0000 Subject: [PATCH 153/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index e803099f5..8d15b2d07 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e803099f5d983055e4b5b00a6baf4bcc91c6f25d +Subproject commit 8d15b2d07c18cc9dfbd550b32ad43e46b2d4014d diff --git a/scripts b/scripts index 274b81ded..7b92cec52 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 274b81ded9c72bff23eca4f0c2a78cb6cb900f8e +Subproject commit 7b92cec5246f97f278e4d608899278d2cafc0992 From 8bb047fcc6a67aa0c0faef880aa1d7dd2ed758a0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 28 May 2022 16:55:20 -0400 Subject: [PATCH 154/854] Remove Notes module Only used in a devel plugin that prints notes, and can be easily replaced with `ui.waypoints.points` --- docs/changelog.txt | 2 + library/CMakeLists.txt | 3 - library/Core.cpp | 6 - library/include/Core.h | 9 - library/include/ModuleFactory.h | 1 - library/include/modules/Notes.h | 65 ------- library/include/modules/Windows.h | 270 ------------------------------ library/modules/Notes.cpp | 53 ------ library/modules/Windows.cpp | 118 ------------- plugins/devel/CMakeLists.txt | 1 - plugins/devel/notes.cpp | 72 -------- 11 files changed, 2 insertions(+), 598 deletions(-) delete mode 100644 library/include/modules/Notes.h delete mode 100644 library/include/modules/Windows.h delete mode 100644 library/modules/Notes.cpp delete mode 100644 library/modules/Windows.cpp delete mode 100644 plugins/devel/notes.cpp diff --git a/docs/changelog.txt b/docs/changelog.txt index 2d5a5cf64..290c4ec3b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -47,6 +47,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Documentation ## API +- Removed ``Notes`` module (C++-only). Access ``ui.waypoints.points`` directly instead. +- Removed ``Windows`` module (C++-only) - unused. ## Lua - ``tile-material``: fix the order of declarations. The ``GetTileMat`` function now returns the material as intended (always returned nil before). Also changed the license info, with permission of the original author. diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 9e7bf8590..440281cf7 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -138,7 +138,6 @@ set(MODULE_HEADERS include/modules/MapCache.h include/modules/Maps.h include/modules/Materials.h - include/modules/Notes.h include/modules/Once.h include/modules/Persistence.h include/modules/Random.h @@ -165,7 +164,6 @@ set(MODULE_SOURCES modules/MapCache.cpp modules/Maps.cpp modules/Materials.cpp - modules/Notes.cpp modules/Once.cpp modules/Persistence.cpp modules/Random.cpp @@ -173,7 +171,6 @@ set(MODULE_SOURCES modules/Screen.cpp modules/Translation.cpp modules/Units.cpp - modules/Windows.cpp modules/World.cpp ) diff --git a/library/Core.cpp b/library/Core.cpp index 10f5057f3..55aa9dc55 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -54,7 +54,6 @@ using namespace std; #include "modules/Gui.h" #include "modules/World.h" #include "modules/Graphic.h" -#include "modules/Windows.h" #include "modules/Persistence.h" #include "RemoteServer.h" #include "RemoteTools.h" @@ -1564,7 +1563,6 @@ Core::Core() : last_local_map_ptr = NULL; last_pause_state = false; top_viewscreen = NULL; - screen_window = NULL; color_ostream::log_errors_to_stderr = true; @@ -1835,8 +1833,6 @@ bool Core::Init() cerr << "Starting DF input capture thread.\n"; // set up hotkey capture d->hotkeythread = std::thread(fHKthread, (void *) temp); - screen_window = new Windows::top_level_window(); - screen_window->addChild(new Windows::dfhack_dummy(5,10)); started = true; modstate = 0; @@ -1984,7 +1980,6 @@ int Core::TileUpdate() { if(!started) return false; - screen_window->paint(); return true; } @@ -2922,5 +2917,4 @@ TYPE * Core::get##TYPE() \ } MODULE_GETTER(Materials); -MODULE_GETTER(Notes); MODULE_GETTER(Graphic); diff --git a/library/include/Core.h b/library/include/Core.h index 1648ae113..f6acd9425 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -58,7 +58,6 @@ namespace DFHack class Process; class Module; class Materials; - class Notes; struct VersionInfo; class VersionInfoFactory; class PluginManager; @@ -69,10 +68,6 @@ namespace DFHack namespace Lua { namespace Core { DFHACK_EXPORT void Reset(color_ostream &out, const char *where); } } - namespace Windows - { - class df_window; - } namespace Screen { @@ -146,8 +141,6 @@ namespace DFHack /// get the materials module Materials * getMaterials(); - /// get the notes module - Notes * getNotes(); /// get the graphic module Graphic * getGraphic(); /// sets the current hotkey command @@ -193,7 +186,6 @@ namespace DFHack std::unique_ptr p; std::shared_ptr vinfo; - DFHack::Windows::df_window * screen_window; static void print(const char *format, ...) Wformat(printf,1,2); static void printerr(const char *format, ...) Wformat(printf,1,2); @@ -242,7 +234,6 @@ namespace DFHack struct { Materials * pMaterials; - Notes * pNotes; Graphic * pGraphic; } s_mods; std::vector> allModules; diff --git a/library/include/ModuleFactory.h b/library/include/ModuleFactory.h index 5c3c149a2..c99e7b328 100644 --- a/library/include/ModuleFactory.h +++ b/library/include/ModuleFactory.h @@ -33,7 +33,6 @@ namespace DFHack { class Module; std::unique_ptr createMaterials(); - std::unique_ptr createNotes(); std::unique_ptr createGraphic(); } #endif diff --git a/library/include/modules/Notes.h b/library/include/modules/Notes.h deleted file mode 100644 index 14bb9db84..000000000 --- a/library/include/modules/Notes.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once -#ifndef CL_MOD_NOTES -#define CL_MOD_NOTES -/** - * \defgroup grp_notes In game notes (and routes) - * @ingroup grp_notes - */ -#include "Export.h" -#include "Module.h" - -#include -#include - -#ifdef __cplusplus -namespace DFHack -{ -#endif - /** - * Game's structure for a note. - * \ingroup grp_notes - */ - struct t_note - { - // First note created has id 0, second has id 1, etc. Not affected - // by lower id notes being deleted. - uint32_t id; // 0 - uint8_t symbol; // 4 - uint8_t unk1; // alignment padding? - uint16_t foreground; // 6 - uint16_t background; // 8 - uint16_t unk2; // alignment padding? - - std::string name; // C - std::string text; // 10 - - uint16_t x; // 14 - uint16_t y; // 16 - uint16_t z; // 18 - - // Is there more? - }; - -#ifdef __cplusplus - - /** - * The notes module - allows reading DF in-game notes - * \ingroup grp_modules - * \ingroup grp_notes - */ - class DFHACK_EXPORT Notes : public Module - { - public: - Notes(); - ~Notes(){}; - bool Finish() - { - return true; - } - std::vector* notes; - }; - -} -#endif // __cplusplus - -#endif diff --git a/library/include/modules/Windows.h b/library/include/modules/Windows.h deleted file mode 100644 index f9b282cf3..000000000 --- a/library/include/modules/Windows.h +++ /dev/null @@ -1,270 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#pragma once -#include "Export.h" -#include - -namespace DFHack -{ -namespace Windows -{ - /* - * DF window stuffs - */ - enum df_color - { - black, - blue, - green, - cyan, - red, - magenta, - brown, - lgray, - dgray, - lblue, - lgreen, - lcyan, - lred, - lmagenta, - yellow, - white - // maybe add transparency? - }; - - // The tile format DF uses internally - struct df_screentile - { - uint8_t symbol; - uint8_t foreground; ///< df_color - uint8_t background; ///< df_color - uint8_t bright; - }; - - - // our silly painter things and window things follow. - class df_window; - struct df_tilebuf - { - df_screentile * data; - unsigned int width; - unsigned int height; - }; - - DFHACK_EXPORT df_screentile *getScreenBuffer(); - - class DFHACK_EXPORT painter - { - friend class df_window; - public: - df_screentile* get(unsigned int x, unsigned int y) - { - if(x >= width || y >= height) - return 0; - return &buffer[x*height + y]; - }; - bool set(unsigned int x, unsigned int y, df_screentile tile ) - { - if(x >= width || y >= height) - return false; - buffer[x*height + y] = tile; - return true; - } - df_color foreground (df_color change = (df_color) -1) - { - if(change != -1) - current_foreground = change; - return current_foreground; - } - df_color background (df_color change = (df_color) -1) - { - if(change != -1) - current_background = change; - return current_background; - } - void bright (bool change) - { - current_bright = change; - } - bool bright () - { - return current_bright; - } - void printStr(std::string & str, bool wrap = false) - { - for ( auto iter = str.begin(); iter != str.end(); iter++) - { - auto elem = *iter; - if(cursor_y >= (int)height) - break; - if(wrap) - { - if(cursor_x >= (int)width) - cursor_x = wrap_column; - } - df_screentile & tile = buffer[cursor_x * height + cursor_y]; - tile.symbol = elem; - tile.foreground = current_foreground; - tile.background = current_background; - tile.bright = current_bright; - cursor_x++; - } - } - void set_wrap (int new_column) - { - wrap_column = new_column; - } - void gotoxy(unsigned int x, unsigned int y) - { - cursor_x = x; - cursor_y = y; - } - void reset() - { - cursor_x = 0; - cursor_y = 0; - current_background = black; - current_foreground = white; - current_bright = false; - wrap_column = 0; - } - private: - painter (df_window * orig, df_screentile * buf, unsigned int width, unsigned int height) - { - origin = orig; - this->width = width; - this->height = height; - this->buffer = buf; - reset(); - } - df_window* origin; - unsigned int width; - unsigned int height; - df_screentile* buffer; - // current paint cursor position - int cursor_x; - int cursor_y; - int wrap_column; - // current foreground color - df_color current_foreground; - // current background color - df_color current_background; - // make bright? - bool current_bright; - }; - - class DFHACK_EXPORT df_window - { - friend class painter; - public: - df_window(int x, int y, unsigned int width, unsigned int height); - virtual ~df_window(); - virtual bool move (int left_, int top_, unsigned int width_, unsigned int height_) = 0; - virtual void paint () = 0; - virtual painter * lock(); - bool unlock (painter * painter); - virtual bool addChild(df_window *); - virtual df_tilebuf getBuffer() = 0; - public: - df_screentile* buffer; - unsigned int width; - unsigned int height; - protected: - df_window * parent; - std::vector children; - int left; - int top; - // FIXME: FAKE - bool locked; - painter * current_painter; - }; - - class DFHACK_EXPORT top_level_window : public df_window - { - public: - top_level_window(); - virtual bool move (int left_, int top_, unsigned int width_, unsigned int height_); - virtual void paint (); - virtual painter * lock(); - virtual df_tilebuf getBuffer(); - }; - class DFHACK_EXPORT buffered_window : public df_window - { - public: - buffered_window(int x, int y, unsigned int width, unsigned int height):df_window(x,y,width, height) - { - buffer = new df_screentile[width*height]; - }; - virtual ~buffered_window() - { - delete buffer; - } - virtual void blit_to_parent () - { - df_tilebuf par = parent->getBuffer(); - for(unsigned xi = 0; xi < width; xi++) - { - for(unsigned yi = 0; yi < height; yi++) - { - unsigned parx = left + xi; - unsigned pary = top + yi; - if(pary >= par.height) continue; - if(parx >= par.width) continue; - par.data[parx * par.height + pary] = buffer[xi * height + yi]; - } - } - } - virtual df_tilebuf getBuffer() - { - df_tilebuf buf; - buf.data = buffer; - buf.width = width; - buf.height = height; - return buf; - }; - }; - class DFHACK_EXPORT dfhack_dummy : public buffered_window - { - public: - dfhack_dummy(int x, int y):buffered_window(x,y,6,1){}; - virtual bool move (int left_, int top_, unsigned int width_, unsigned int height_) - { - top = top_; - left = left_; - return true; - } - virtual void paint () - { - painter * p = lock(); - p->bright(true); - p->background(black); - p->foreground(white); - std::string dfhack = "DFHack"; - p->printStr(dfhack); - blit_to_parent(); - } - }; -} -} diff --git a/library/modules/Notes.cpp b/library/modules/Notes.cpp deleted file mode 100644 index 04fb59e4d..000000000 --- a/library/modules/Notes.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* -www.sourceforge.net/projects/dfhack -Copyright (c) 2009 Petr Mrázek (peterix), Kenneth Ferland (Impaler[WrG]), dorf - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#include "Internal.h" - -#include -#include -#include -using namespace std; - -#include "VersionInfo.h" -#include "Types.h" -#include "Error.h" -#include "MemAccess.h" -#include "MiscUtils.h" -#include "ModuleFactory.h" -#include "Core.h" -#include "modules/Notes.h" -#include -#include "df/ui.h" -using namespace DFHack; - -std::unique_ptr DFHack::createNotes() -{ - return dts::make_unique(); -} - -// FIXME: not even a wrapper now -Notes::Notes() -{ - notes = (std::vector*) &df::global::ui->waypoints.points; -} diff --git a/library/modules/Windows.cpp b/library/modules/Windows.cpp deleted file mode 100644 index af5368e50..000000000 --- a/library/modules/Windows.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#include "Export.h" -#include "Module.h" -#include "BitArray.h" -#include - -#include "DataDefs.h" -#include "df/init.h" -#include "df/ui.h" -#include -#include "modules/Windows.h" - -using namespace DFHack; -using df::global::gps; - -Windows::df_screentile *Windows::getScreenBuffer() -{ - if (!gps) return NULL; - return (df_screentile *) gps->screen; -} - -Windows::df_window::df_window(int x, int y, unsigned int width, unsigned int height) -:buffer(0), width(width), height(height), parent(0), left(x), top(y), current_painter(NULL) -{ - buffer = 0; -}; -Windows::df_window::~df_window() -{ - for(auto iter = children.begin();iter != children.end();iter++) - { - delete *iter; - } - children.clear(); -}; -Windows::painter * Windows::df_window::lock() -{ - locked = true; - current_painter = new Windows::painter(this,buffer,width, height); - return current_painter; -}; - -bool Windows::df_window::addChild( df_window * child) -{ - children.push_back(child); - child->parent = this; - return true; -} - -bool Windows::df_window::unlock (painter * painter) -{ - if(current_painter == painter) - { - delete current_painter; - current_painter = 0; - locked = false; - return true; - } - return false; -} - -Windows::top_level_window::top_level_window() : df_window(0,0,gps ? gps->dimx : 80,gps ? gps->dimy : 25) -{ - buffer = 0; -} - -bool Windows::top_level_window::move (int left_, int top_, unsigned int width_, unsigned int height_) -{ - width = width_; - height = height_; - // what if we are painting already? Is that possible? - return true; -}; - -Windows::painter * Windows::top_level_window::lock() -{ - buffer = getScreenBuffer(); - return df_window::lock(); -} - -void Windows::top_level_window::paint () -{ - for(auto iter = children.begin();iter != children.end();iter++) - { - (*iter)->paint(); - } -}; - -Windows::df_tilebuf Windows::top_level_window::getBuffer() -{ - df_tilebuf buf; - buf.data = getScreenBuffer(); - buf.height = df::global::gps->dimy; - buf.width = df::global::gps->dimx; - return buf; -} diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 6d15f09dd..d1b84614f 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -13,7 +13,6 @@ dfhack_plugin(eventExample eventExample.cpp) dfhack_plugin(frozen frozen.cpp) dfhack_plugin(kittens kittens.cpp LINK_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) dfhack_plugin(memview memview.cpp memutils.cpp LINK_LIBRARIES lua) -dfhack_plugin(notes notes.cpp) dfhack_plugin(onceExample onceExample.cpp) dfhack_plugin(renderer-msg renderer-msg.cpp) dfhack_plugin(rprobe rprobe.cpp) diff --git a/plugins/devel/notes.cpp b/plugins/devel/notes.cpp deleted file mode 100644 index 33b768af7..000000000 --- a/plugins/devel/notes.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "Core.h" -#include -#include -#include -#include -#include -#include - -using std::vector; -using std::string; -using namespace DFHack; - -command_result df_notes (color_ostream &out, vector & parameters); - -DFHACK_PLUGIN("notes"); - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand("dumpnotes", - "Dumps in-game notes", - df_notes)); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -command_result df_notes (color_ostream &con, vector & parameters) -{ - CoreSuspender suspend; - - DFHack::Notes * note_mod = Core::getInstance().getNotes(); - std::vector* note_list = note_mod->notes; - - if (note_list == NULL) - { - con.printerr("Notes are not supported under this version of DF.\n"); - return CR_OK; - } - - if (note_list->empty()) - { - con << "There are no notes." << std::endl; - return CR_OK; - } - - - for (size_t i = 0; i < note_list->size(); i++) - { - t_note* note = (*note_list)[i]; - - con.print("Note %p at: %d/%d/%d\n",note, note->x, note->y, note->z); - con.print("Note id: %d\n", note->id); - con.print("Note symbol: '%c'\n", note->symbol); - - if (note->name.length() > 0) - con << "Note name: " << (note->name) << std::endl; - if (note->text.length() > 0) - con << "Note text: " << (note->text) << std::endl; - - if (note->unk1 != 0) - con.print("unk1: %x\n", note->unk1); - if (note->unk2 != 0) - con.print("unk2: %x\n", note->unk2); - - con << std::endl; - } - - return CR_OK; -} From 4c7caa2658a29c77841bb7fa401ce58e6b000ae8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 4 Jun 2022 12:57:18 -0400 Subject: [PATCH 155/854] Remove unneeded dependencies on modules/Graphic.h --- docs/changelog.txt | 1 + library/CMakeLists.txt | 2 - library/Core.cpp | 1 - library/PlugLoad-windows.cpp | 1 - library/include/DFHack.h | 1 - library/include/Hooks.h | 1 - library/include/modules/Engravings.h | 63 ---------------------- library/modules/Engravings.cpp | 78 ---------------------------- 8 files changed, 1 insertion(+), 147 deletions(-) delete mode 100644 library/include/modules/Engravings.h delete mode 100644 library/modules/Engravings.cpp diff --git a/docs/changelog.txt b/docs/changelog.txt index 290c4ec3b..8f663d9bc 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -47,6 +47,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Documentation ## API +- Removed ``Engravings`` module (C++-only). Access ``world.engravings`` directly instead. - Removed ``Notes`` module (C++-only). Access ``ui.waypoints.points`` directly instead. - Removed ``Windows`` module (C++-only) - unused. diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 440281cf7..c9e9cd844 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -126,7 +126,6 @@ set(MODULE_HEADERS include/modules/Burrows.h include/modules/Constructions.h include/modules/Designations.h - include/modules/Engravings.h include/modules/EventManager.h include/modules/Filesystem.h include/modules/Graphic.h @@ -153,7 +152,6 @@ set(MODULE_SOURCES modules/Burrows.cpp modules/Constructions.cpp modules/Designations.cpp - modules/Engravings.cpp modules/EventManager.cpp modules/Filesystem.cpp modules/Graphic.cpp diff --git a/library/Core.cpp b/library/Core.cpp index 55aa9dc55..cfb029d57 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -53,7 +53,6 @@ using namespace std; #include "modules/Filesystem.h" #include "modules/Gui.h" #include "modules/World.h" -#include "modules/Graphic.h" #include "modules/Persistence.h" #include "RemoteServer.h" #include "RemoteTools.h" diff --git a/library/PlugLoad-windows.cpp b/library/PlugLoad-windows.cpp index 96c2e900a..848d25f50 100644 --- a/library/PlugLoad-windows.cpp +++ b/library/PlugLoad-windows.cpp @@ -35,7 +35,6 @@ distribution. #include #include "tinythread.h" -#include "modules/Graphic.h" #include "../plugins/uicommon.h" /* diff --git a/library/include/DFHack.h b/library/include/DFHack.h index 8a094cf86..0a5183adc 100644 --- a/library/include/DFHack.h +++ b/library/include/DFHack.h @@ -54,7 +54,6 @@ distribution. // DFHack modules #include "modules/Buildings.h" -#include "modules/Engravings.h" #include "modules/Materials.h" #include "modules/Constructions.h" #include "modules/Units.h" diff --git a/library/include/Hooks.h b/library/include/Hooks.h index f5ef7079c..7a266e92c 100644 --- a/library/include/Hooks.h +++ b/library/include/Hooks.h @@ -47,7 +47,6 @@ namespace SDL // these functions are here because they call into DFHack::Core and therefore need to // be declared as friend functions/known #ifdef _DARWIN -#include "modules/Graphic.h" DFhackCExport int DFH_SDL_NumJoysticks(void); DFhackCExport void DFH_SDL_Quit(void); DFhackCExport int DFH_SDL_PollEvent(SDL::Event* event); diff --git a/library/include/modules/Engravings.h b/library/include/modules/Engravings.h deleted file mode 100644 index bf30c62a8..000000000 --- a/library/include/modules/Engravings.h +++ /dev/null @@ -1,63 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#pragma once -#ifndef CL_MOD_ENGRAVINGS -#define CL_MOD_ENGRAVINGS -/* -* DF engravings -*/ -#include "Export.h" -#include "DataDefs.h" -#include "df/engraving.h" -/** - * \defgroup grp_engraving Engraving module parts - * @ingroup grp_modules - */ -namespace DFHack -{ -namespace Engravings -{ -// "Simplified" copy of engraving -struct t_engraving { - int32_t artist; - int32_t masterpiece_event; - int32_t skill_rating; - df::coord pos; - df::engraving_flags flags; - int8_t tile; - int32_t art_id; - int16_t art_subid; - df::item_quality quality; - // Pointer to original object, in case you want to modify it - df::engraving *origin; -}; - -DFHACK_EXPORT bool isValid(); -DFHACK_EXPORT uint32_t getCount(); -DFHACK_EXPORT bool copyEngraving (const int32_t index, t_engraving &out); -DFHACK_EXPORT df::engraving * getEngraving (const int32_t index); -} -} -#endif diff --git a/library/modules/Engravings.cpp b/library/modules/Engravings.cpp deleted file mode 100644 index c2e0e6fce..000000000 --- a/library/modules/Engravings.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - - -#include "Internal.h" - -#include -#include -#include -using namespace std; - -#include "VersionInfo.h" -#include "MemAccess.h" -#include "Types.h" -#include "Core.h" - -#include "modules/Engravings.h" -#include "df/world.h" - -using namespace DFHack; -using df::global::world; - -bool Engravings::isValid() -{ - return (world != NULL); -} - -uint32_t Engravings::getCount() -{ - return world->engravings.size(); -} - -df::engraving * Engravings::getEngraving(int index) -{ - if (uint32_t(index) >= getCount()) - return NULL; - return world->engravings[index]; -} - -bool Engravings::copyEngraving(const int32_t index, t_engraving &out) -{ - if (uint32_t(index) >= getCount()) - return false; - - out.origin = world->engravings[index]; - - out.artist = out.origin->artist; - out.masterpiece_event = out.origin->masterpiece_event; - out.skill_rating = out.origin->skill_rating; - out.pos = out.origin->pos; - out.flags = out.origin->flags; - out.tile = out.origin->tile; - out.art_id = out.origin->art_id; - out.art_subid = out.origin->art_subid; - out.quality = out.origin->quality; - return true; -} From 1147add5207c730c2b162020347518144b451ddf Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Jun 2022 23:44:34 -0400 Subject: [PATCH 156/854] Constructions module: remove some old/unused functions/types --- docs/changelog.txt | 1 + library/include/modules/Constructions.h | 16 ------------ library/modules/Constructions.cpp | 33 ------------------------- 3 files changed, 1 insertion(+), 49 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 8f663d9bc..a06629f62 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -50,6 +50,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Removed ``Engravings`` module (C++-only). Access ``world.engravings`` directly instead. - Removed ``Notes`` module (C++-only). Access ``ui.waypoints.points`` directly instead. - Removed ``Windows`` module (C++-only) - unused. +- ``Constructions`` module (C++-only): removed ``t_construction``, ``isValid()``, ``getCount()``, ``getConstruction()``, and ``copyConstruction()``. Access ``world.constructions`` directly instead. ## Lua - ``tile-material``: fix the order of declarations. The ``GetTileMat`` function now returns the material as intended (always returned nil before). Also changed the license info, with permission of the original author. diff --git a/library/include/modules/Constructions.h b/library/include/modules/Constructions.h index 3831d4bb1..c7a1c048b 100644 --- a/library/include/modules/Constructions.h +++ b/library/include/modules/Constructions.h @@ -42,23 +42,7 @@ namespace DFHack { namespace Constructions { -// "Simplified" copy of construction -struct t_construction { - df::coord pos; - df::item_type item_type; - int16_t item_subtype; - int16_t mat_type; - int32_t mat_index; - df::construction_flags flags; - int16_t original_tile; - // Pointer to original object, in case you want to modify it - df::construction *origin; -}; -DFHACK_EXPORT bool isValid(); -DFHACK_EXPORT uint32_t getCount(); -DFHACK_EXPORT bool copyConstruction (const int32_t index, t_construction &out); -DFHACK_EXPORT df::construction * getConstruction (const int32_t index); DFHACK_EXPORT df::construction * findAtTile(df::coord pos); DFHACK_EXPORT bool designateNew(df::coord pos, df::construction_type type, diff --git a/library/modules/Constructions.cpp b/library/modules/Constructions.cpp index 9cec2eab9..5fc632500 100644 --- a/library/modules/Constructions.cpp +++ b/library/modules/Constructions.cpp @@ -51,22 +51,6 @@ using namespace DFHack; using namespace df::enums; using df::global::world; -bool Constructions::isValid() -{ - return (world != NULL); -} - -uint32_t Constructions::getCount() -{ - return world->constructions.size(); -} - -df::construction * Constructions::getConstruction(const int32_t index) -{ - if (uint32_t(index) >= getCount()) - return NULL; - return world->constructions[index]; -} df::construction * Constructions::findAtTile(df::coord pos) { @@ -77,23 +61,6 @@ df::construction * Constructions::findAtTile(df::coord pos) return NULL; } -bool Constructions::copyConstruction(const int32_t index, t_construction &out) -{ - if (uint32_t(index) >= getCount()) - return false; - - out.origin = world->constructions[index]; - - out.pos = out.origin->pos; - out.item_type = out.origin->item_type; - out.item_subtype = out.origin->item_subtype; - out.mat_type = out.origin->mat_type; - out.mat_index = out.origin->mat_index; - out.flags = out.origin->flags; - out.original_tile = out.origin->original_tile; - return true; -} - bool Constructions::designateNew(df::coord pos, df::construction_type type, df::item_type item, int mat_index) { From 739871bc0fafe1a80f498c88447ae5c9b912b5e0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jun 2022 00:03:49 -0400 Subject: [PATCH 157/854] Remove Hooks-egg.cpp and related code and configuration options --- CMakeLists.txt | 2 - docs/changelog.txt | 1 + library/CMakeLists.txt | 42 ++++--------------- library/Core.cpp | 7 ---- library/Hooks-egg.cpp | 92 ----------------------------------------- library/include/Core.h | 7 ---- library/include/Hooks.h | 18 -------- 7 files changed, 10 insertions(+), 159 deletions(-) delete mode 100644 library/Hooks-egg.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8271840e6..d01d105c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,11 +206,9 @@ set(DFHACK_BUILD_ID "" CACHE STRING "Build ID (should be specified on command li if(UNIX) # put the lib into DF/hack set(DFHACK_LIBRARY_DESTINATION hack) - set(DFHACK_EGGY_DESTINATION libs) else() # windows is crap, therefore we can't do nice things with it. leave the libs on a nasty pile... set(DFHACK_LIBRARY_DESTINATION .) - set(DFHACK_EGGY_DESTINATION .) endif() # external tools will be installed here: diff --git a/docs/changelog.txt b/docs/changelog.txt index a06629f62..9ff01740f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -47,6 +47,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Documentation ## API +- Removed "egg" ("eggy") hook support (Linux only). The only remaining method of hooking into DF is by interposing SDL calls, which has been the method used by all binary releases of DFHack. - Removed ``Engravings`` module (C++-only). Access ``world.engravings`` directly instead. - Removed ``Notes`` module (C++-only). Access ``ui.waypoints.points`` directly instead. - Removed ``Windows`` module (C++-only) - unused. diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index c9e9cd844..95a8e610a 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -114,13 +114,6 @@ set(MAIN_SOURCES_DARWIN Process-darwin.cpp ) -set(MAIN_SOURCES_LINUX_EGGY - ${CONSOLE_SOURCES} - Hooks-egg.cpp - PlugLoad-linux.cpp - Process-linux.cpp -) - set(MODULE_HEADERS include/modules/Buildings.h include/modules/Burrows.h @@ -206,10 +199,7 @@ list(APPEND PROJECT_SOURCES ${MAIN_SOURCES}) list(APPEND PROJECT_SOURCES ${MODULE_SOURCES}) if(UNIX) - option(BUILD_EGGY "Make DFHack strangely egg-shaped." OFF) - if(BUILD_EGGY) - list(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX_EGGY}) - elseif(APPLE) + if(APPLE) list(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN}) else() list(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX}) @@ -371,15 +361,9 @@ add_executable(dfhack-run dfhack-run.cpp) add_executable(binpatch binpatch.cpp) target_link_libraries(binpatch dfhack-md5) -if(BUILD_EGGY) - set_target_properties(dfhack PROPERTIES OUTPUT_NAME "egg" ) -else() - if(WIN32) - set_target_properties(dfhack PROPERTIES OUTPUT_NAME "SDL" ) - endif() -endif() - if(WIN32) + # name the resulting library SDL.dll on Windows + set_target_properties(dfhack PROPERTIES OUTPUT_NAME "SDL" ) set_target_properties(dfhack PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) set_target_properties(dfhack-client PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) else() @@ -434,23 +418,15 @@ if(UNIX) DESTINATION .) endif() else() - if(NOT BUILD_EGGY) - # On windows, copy the renamed SDL so DF can still run. - install(PROGRAMS ${dfhack_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}/SDLreal.dll - DESTINATION ${DFHACK_LIBRARY_DESTINATION}) - endif() + # On windows, copy the renamed SDL so DF can still run. + install(PROGRAMS ${dfhack_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}/SDLreal.dll + DESTINATION ${DFHACK_LIBRARY_DESTINATION}) endif() # install the main lib -if(NOT BUILD_EGGY) - install(TARGETS dfhack - LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} - RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) -else() - install(TARGETS dfhack - LIBRARY DESTINATION ${DFHACK_EGGY_DESTINATION} - RUNTIME DESTINATION ${DFHACK_EGGY_DESTINATION}) -endif() +install(TARGETS dfhack + LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} + RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) # install the offset file install(FILES xml/symbols.xml diff --git a/library/Core.cpp b/library/Core.cpp index cfb029d57..941d1bebf 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1975,13 +1975,6 @@ bool Core::isSuspended(void) return ownerThread.load() == std::this_thread::get_id(); } -int Core::TileUpdate() -{ - if(!started) - return false; - return true; -} - void Core::doUpdate(color_ostream &out, bool first_update) { Lua::Core::Reset(out, "DF code execution"); diff --git a/library/Hooks-egg.cpp b/library/Hooks-egg.cpp deleted file mode 100644 index c98cf5da2..000000000 --- a/library/Hooks-egg.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ -#include -#include -#include -#include -#include -#include -#include - -#include "Core.h" -#include "Hooks.h" -#include - -// hook - called before rendering -DFhackCExport int egg_init(void) -{ - // reroute stderr - freopen("stderr.log", "w", stderr); - // we don't reroute stdout until we figure out if this should be done at all - // See: Console-linux.cpp - fprintf(stderr,"dfhack: hooking successful\n"); - return true; -} - -// hook - called before rendering -DFhackCExport int egg_shutdown(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - return c.Shutdown(); -} - -// hook - called for each game tick (or more often) -DFhackCExport int egg_tick(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - return c.Update(); -} -// hook - called before rendering -DFhackCExport int egg_prerender(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - return c.TileUpdate(); -} - -// hook - called for each SDL event, returns 0 when the event has been consumed. 1 otherwise -DFhackCExport int egg_sdl_event(SDL::Event* event) -{ - // if the event is valid, intercept - if( event != 0 ) - { - DFHack::Core & c = DFHack::Core::getInstance(); - return c.DFH_SDL_Event(event); - } - return true; -} - -// return this if you want to kill the event. -const int curses_error = -1; -// hook - ncurses event, -1 signifies error. -DFhackCExport int egg_curses_event(int orig_return) -{ - /* - if(orig_return != -1) - { - DFHack::Core & c = DFHack::Core::getInstance(); - int out; - return c.ncurses_wgetch(orig_return,); - }*/ - return orig_return; -} diff --git a/library/include/Core.h b/library/include/Core.h index f6acd9425..531d1b581 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -121,12 +121,6 @@ namespace DFHack friend int ::SDL_Init(uint32_t flags); friend int ::wgetch(WINDOW * w); #endif - friend int ::egg_init(void); - friend int ::egg_shutdown(void); - friend int ::egg_tick(void); - friend int ::egg_prerender(void); - friend int ::egg_sdl_event(SDL::Event* event); - friend int ::egg_curses_event(int orig_return); public: /// Get the single Core instance or make one. static Core& getInstance() @@ -205,7 +199,6 @@ namespace DFHack bool Init(); int Update (void); - int TileUpdate (void); int Shutdown (void); int DFH_SDL_Event(SDL::Event* event); bool ncurses_wgetch(int in, int & out); diff --git a/library/include/Hooks.h b/library/include/Hooks.h index 7a266e92c..d17b96acf 100644 --- a/library/include/Hooks.h +++ b/library/include/Hooks.h @@ -74,21 +74,3 @@ DFhackCExport void * SDL_GetVideoSurface(void); DFhackCExport int SDL_SemWait(vPtr sem); DFhackCExport int SDL_SemPost(vPtr sem); - -// hook - called early from DF's main() -DFhackCExport int egg_init(void); - -// hook - called before rendering -DFhackCExport int egg_shutdown(void); - -// hook - called for each game tick (or more often) -DFhackCExport int egg_tick(void); - -// hook - called before rendering -DFhackCExport int egg_prerender(void); - -// hook - called for each SDL event, can filter both the event and the return value -DFhackCExport int egg_sdl_event(SDL::Event* event); - -// hook - ncurses event. return -1 to consume -DFhackCExport int egg_curses_event(int orig_return); From c026bd6dcb659cef31e2481bb38a1d01aaec22c5 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Fri, 1 Jul 2022 12:04:18 +0100 Subject: [PATCH 158/854] Split sections a bit --- docs/guides/modding-guide.rst | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index d3bf384c8..cb37de575 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -3,20 +3,17 @@ DFHack modding guide ==================== -Preface -------- +What is a mod/script? +--------------------- -Why not just use raw modding? - -For many things it's either completely and only (sensibly) doable in raws or completely and only doable with DFHack. For mods where DFHack is an alternative and not the only option, it's much less hacky, easier to maintain, and easier to extend, and is not prone to side-effects. A great example is adding a syndrome when a reaction is performed requiring an exploding boulder in raws but having dedicated tools for it if you use DFHack. Many things will require a mix of raw modding and DFHack. - -What kind of mods can we make? +A script is a single file that can be run as a command in DFHack, like something that modifies or displays game data on request. A mod is something you install to get persistent behavioural changes in the game and/or add new content. DFHack mods contain and use scripts as well as often having a raw mod component. -Lots of things, and the list grows as more and more hooks and tools are developed within DFHack. You can modify behaviours by cleverly watching and modifying certain areas of the game with functions that run on every tick or by using hooks. Familiarising yourself with the many structs of the game will help with ideas immensely, and you can always ask for help in the right places (e.g. DFHack's Discord). +DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at https://www.lua.org/pil/1.html. -Examples of things we have mod tools to allow are directly changing skills, spawning liquids, adding new powered buildings, creating items/trees/units based on various conditions (in reactions or similar), running code when certain projectiles hit or move, and much else besides. +Why not just use raw modding? +----------------------------- -DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at https://www.lua.org/pil/1.html. +For many things it's either completely and only (sensibly) doable in raws or completely and only doable with DFHack. For mods where DFHack is an alternative and not the only option, it's much less hacky, easier to maintain, and easier to extend, and is not prone to side-effects. A great example is adding a syndrome when a reaction is performed requiring an exploding boulder in raws but having dedicated tools for it if you use DFHack. Many things will require a mix of raw modding and DFHack. A mod-maker's development environment ------------------------------------- From 112d2938dfa2e47ac0ddec4ac6582f73866854fc Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 1 Jul 2022 15:58:24 +0000 Subject: [PATCH 159/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 7b92cec52..9480cad97 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 7b92cec5246f97f278e4d608899278d2cafc0992 +Subproject commit 9480cad97c3c0e71f8cb76004c74e2912702af95 From 04e0b600a520ba5cd0e9af47f58f95d7d3c27689 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Wed, 22 Jun 2022 12:33:54 +0200 Subject: [PATCH 160/854] add keybinding for `gui/workorder-details` to dfhack.init-example --- dfhack.init-example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dfhack.init-example b/dfhack.init-example index bcc5615d6..88528f650 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -68,6 +68,9 @@ keybinding add Ctrl-Shift-R view-unit-reports # view extra unit information keybinding add Alt-I@dwarfmode/ViewUnits|unitlist gui/unit-info-viewer +# set workorder item details (on workorder details screen press D again) +keybinding add D@workquota_details gui/workorder-details + ############################## # Generic adv mode bindings # ############################## From 46d0f36d412eedf2bd02bbd7ac901cb29536f095 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Fri, 1 Jul 2022 19:10:26 +0100 Subject: [PATCH 161/854] Pad out Getting used to gm-editor and DFStructs exploration a bit --- docs/guides/modding-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index cb37de575..c35e2a41a 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -58,9 +58,9 @@ Simple. Save this as a Lua file in your own scripts directory and run it as show Getting used to gm-editor and DFStructs exploration --------------------------------------------------- -So how could you have known about the field and type we just used? +So how could you have known about the field and type we just used? Well, there are two main tools for discovering the various fields in the game's data structures. The first is the ``df-structures`` repository (at https://github.com/DFHack/df-structures) that contains XML files denoting the contents of the game's structures. The second is the script ``gui/gm-editor`` which is an interactive data explorer. You can run the script while objects like units are selected to view the data within them. You can also pass ``scr`` as an argument to the script to view the data for the current screen. Press ? while the script is active to view help. -s +Familiarising yourself with the many structs of the game will help with ideas immensely, and you can always ask for help in the right places (e.g. DFHack's Discord). Detecting triggers ------------------ From b86d16d64ceb76745179936771eea724c2476b12 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Sun, 3 Jul 2022 15:59:01 +0100 Subject: [PATCH 162/854] Some of Detecting Triggers --- docs/guides/modding-guide.rst | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index c35e2a41a..9bbdc9bfd 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -65,7 +65,30 @@ Familiarising yourself with the many structs of the game will help with ideas im Detecting triggers ------------------ -s +One main method for getting new behaviour into the game is callback functions. There are two main libraries for this, ``repeat-util`` and ``eventful``. ``repeat-util`` is used to run a function once per configurable number of frames (paused or unpaused), ticks (unpaused), in-game days, months, or years. For adding behaviour you will most often want something to run once a tick. ``eventful`` is used to get code to run (with special parameters!) when something happens in the game, like a reaction or job being completed or a projectile moving. + +To get something to run once per tick, we would want to call ``repeat-util``'s ``scheduleEvery`` function. + +First, we load the module: :: + + local repeatUtil = require("repeat-util") + +Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. It's recommended to use something like a mod id. :: + + local modId = "my-test-mod" + +Then, we pass the key, amount of time units between function calls, what the time units are, and finally the callback function itself: + + repeatUtil.scheduleEvery(modId, 1, "ticks", function() + -- Do something like iterating over all units + for _, unit in ipairs(df.global.world.units.all) do + print(unit.id) + end + end) + +``eventful`` is slightly more involved: + +TODO Setting up an environment for a more advanced modular mod --------------------------------------------------------- From f8a8bf6e2993a2ac5a548e2406223a9757b154e6 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Sun, 3 Jul 2022 17:33:36 +0100 Subject: [PATCH 163/854] Some more --- docs/guides/modding-guide.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 9bbdc9bfd..c6ae8ae15 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -77,7 +77,7 @@ Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. It' local modId = "my-test-mod" -Then, we pass the key, amount of time units between function calls, what the time units are, and finally the callback function itself: +Then, we pass the key, amount of time units between function calls, what the time units are, and finally the callback function itself: :: repeatUtil.scheduleEvery(modId, 1, "ticks", function() -- Do something like iterating over all units @@ -86,9 +86,12 @@ Then, we pass the key, amount of time units between function calls, what the tim end end) -``eventful`` is slightly more involved: +``eventful`` is slightly more involved. :: -TODO + local eventful = require("plugins.eventful") + -- blah + +Check the full list of Eventful events at https://docs.dfhack.org/en/stable/docs/Lua%20API.html#list-of-events. Setting up an environment for a more advanced modular mod --------------------------------------------------------- From 0237567c183f7a50305b2db47a2c0902a8eb3189 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Mon, 4 Jul 2022 17:11:21 +0100 Subject: [PATCH 164/854] More modding guide --- docs/guides/modding-guide.rst | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index c6ae8ae15..b3b5c515b 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -86,17 +86,24 @@ Then, we pass the key, amount of time units between function calls, what the tim end end) -``eventful`` is slightly more involved. :: +``eventful`` is slightly more involved. First get the module: :: local eventful = require("plugins.eventful") - -- blah -Check the full list of Eventful events at https://docs.dfhack.org/en/stable/docs/Lua%20API.html#list-of-events. +``eventful`` contains a table for each event which you populate with functions. Each function in the table is then called with the appropriate arguments when the event occurs. So, for example, to print the position of a moving (item) projectile: :: + + eventful.onProjItemCheckMovement[modId] = function(projectile) + print(projectile.cur_pos.x, projectile.cur_pos.y, projectile.cur_pos.z) + end + +Check the full list of events at https://docs.dfhack.org/en/stable/docs/Lua%20API.html#list-of-events. Setting up an environment for a more advanced modular mod --------------------------------------------------------- -s +Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. + +TODO Your first whole mod -------------------- From 69b6de20456d986c778dd88ba0d86031937633c8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Jul 2022 01:19:30 -0400 Subject: [PATCH 165/854] [pre-commit.ci] pre-commit autoupdate (#2235) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/python-jsonschema/check-jsonschema: 0.16.0 → 0.16.2](https://github.com/python-jsonschema/check-jsonschema/compare/0.16.0...0.16.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 774c42a95..4e7a4f48b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.16.0 + rev: 0.16.2 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks From aec56848976ce5334aa0dedb5b3219ec4d7a70c0 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 5 Jul 2022 07:17:21 +0000 Subject: [PATCH 166/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 8d15b2d07..219676497 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 8d15b2d07c18cc9dfbd550b32ad43e46b2d4014d +Subproject commit 2196764977011991127244b28ff13b90cef19af3 diff --git a/scripts b/scripts index 9480cad97..f08794136 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 9480cad97c3c0e71f8cb76004c74e2912702af95 +Subproject commit f08794136e20705b87cfaf509b182f07ec394238 From fabcf7c9795f3583acbf77f634256a59fb54e44c Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 6 Jul 2022 07:16:58 +0000 Subject: [PATCH 167/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index f08794136..6bc683d2c 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit f08794136e20705b87cfaf509b182f07ec394238 +Subproject commit 6bc683d2ca42ef467465b0905513ad590a7dc4c2 From 091068c7102c8f5ddb7133cfe1b07133f5e818cb Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 6 Jul 2022 06:57:13 -0700 Subject: [PATCH 168/854] [prospect] give player control over which information is output (#2231) * give player control over prospect output * suspend the core *before* we call to Lua --- docs/Plugins.rst | 58 +++++-- docs/changelog.txt | 2 +- plugins/CMakeLists.txt | 2 +- plugins/lua/prospector.lua | 45 ++++++ plugins/prospector.cpp | 322 ++++++++++++++++++++++++------------- 5 files changed, 299 insertions(+), 130 deletions(-) create mode 100644 plugins/lua/prospector.lua diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 3b42bc16a..e5c662ffa 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -373,29 +373,55 @@ selected objects. prospect ======== -Prints a big list of all the present minerals and plants. By default, only -the visible part of the map is scanned. -Options: +**Usage:** -:all: Scan the whole map, as if it were revealed. -:value: Show material value in the output. Most useful for gems. -:hell: Show the Z range of HFS tubes. Implies 'all'. + ``prospect [all|hell] []`` -If prospect is called during the embark selection screen, it displays an estimate of -layer stone availability. +Shows a summary of resources that exist on the map. By default, only the visible +part of the map is scanned. Include the ``all`` keyword if you want ``prospect`` +to scan the whole map as if it were revealed. Use ``hell`` instead of ``all`` if +you also want to see the Z range of HFS tubes in the 'features' report section. -.. note:: +**Options:** - The results of pre-embark prospect are an *estimate*, and can at best be expected - to be somewhere within +/- 30% of the true amount; sometimes it does a lot worse. - Especially, it is not clear how to precisely compute how many soil layers there - will be in a given embark tile, so it can report a whole extra layer, or omit one - that is actually present. +:``-h``, ``--help``: + Shows this help text. +:``-s``, ``--show ``: + Shows only the named comma-separated list of report sections. Report section + names are: summary, liquids, layers, features, ores, gems, veins, shrubs, + and trees. If run during pre-embark, only the layers, ores, gems, and veins + report sections are available. +:``-v``, ``--values``: + Includes material value in the output. Most useful for the 'gems' report + section. -Options: +**Examples:** + +``prospect all`` + Shows the entire report for the entire map. + +``prospect hell --show layers,ores,veins`` + Shows only the layers, ores, and other vein stone report sections, and + includes information on HFS tubes when a fort is loaded. + +``prospect all -sores`` + Show only information about ores for the pre-embark or fortress map report. + +**Pre-embark estimate:** + +If prospect is called during the embark selection screen, it displays an +estimate of layer stone availability. If the ``all`` keyword is specified, it +also estimates ores, gems, and vein material. The estimate covers all tiles of +the embark rectangle. + +.. note:: -:all: Also estimate vein mineral amounts. + The results of pre-embark prospect are an *estimate*, and can at best be + expected to be somewhere within +/- 30% of the true amount; sometimes it + does a lot worse. Especially, it is not clear how to precisely compute how + many soil layers there will be in a given embark tile, so it can report a + whole extra layer, or omit one that is actually present. .. _remotefortressreader: diff --git a/docs/changelog.txt b/docs/changelog.txt index 2d5a5cf64..0c1422306 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,8 +41,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled ## Misc Improvements - - ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. +- `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. ## Documentation diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index ed9404b8e..4a4b74eaa 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -148,7 +148,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(petcapRemover petcapRemover.cpp) dfhack_plugin(plants plants.cpp) dfhack_plugin(probe probe.cpp) - dfhack_plugin(prospector prospector.cpp) + dfhack_plugin(prospector prospector.cpp LINK_LIBRARIES lua) dfhack_plugin(power-meter power-meter.cpp LINK_LIBRARIES lua) dfhack_plugin(regrass regrass.cpp) add_subdirectory(remotefortressreader) diff --git a/plugins/lua/prospector.lua b/plugins/lua/prospector.lua new file mode 100644 index 000000000..403a1f43f --- /dev/null +++ b/plugins/lua/prospector.lua @@ -0,0 +1,45 @@ +local _ENV = mkmodule('plugins.prospector') + +local argparse = require('argparse') +local utils = require('utils') + +local VALID_SHOW_VALUES = utils.invert{ + 'summary', 'liquids', 'layers', 'features', 'ores', + 'gems', 'veins', 'shrubs', 'trees' +} + +function parse_commandline(opts, ...) + local show = {} + local positionals = argparse.processArgsGetopt({...}, { + {'h', 'help', handler=function() opts.help = true end}, + {'s', 'show', hasArg=true, handler=function(optarg) + show = argparse.stringList(optarg) end}, + {'v', 'values', handler=function() opts.value = true end}, + }) + + for _,p in ipairs(positionals) do + if p == 'all' then opts.hidden = true + elseif p == 'hell' then + opts.hidden = true + opts.tube = true + else + qerror(('unknown keyword: "%s"'):format(p)) + end + end + + if #show > 0 then + for s in pairs(VALID_SHOW_VALUES) do + opts[s] = false + end + end + + for _,s in ipairs(show) do + if VALID_SHOW_VALUES[s] then + opts[s] = true + else + qerror(('unknown report section: "%s"'):format(s)) + end + end +end + +return _ENV diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 6b2079cf0..0516dd552 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -17,6 +17,7 @@ using namespace std; #include "Core.h" #include "Console.h" #include "Export.h" +#include "LuaTools.h" #include "PluginManager.h" #include "modules/Gui.h" #include "modules/MapCache.h" @@ -44,6 +45,50 @@ using df::coord2d; DFHACK_PLUGIN("prospector"); REQUIRE_GLOBAL(world); +struct prospect_options { + // whether to display help + bool help = false; + + // whether to scan the whole map or just the unhidden tiles + bool hidden = false; + + // whether to also show material values + bool value = false; + + // whether to show adamantine tube z-levels + bool tube = false; + + // which report sections to show + bool summary = true; + bool liquids = true; + bool layers = true; + bool features = true; + bool ores = true; + bool gems = true; + bool veins = true; + bool shrubs = true; + bool trees = true; + + static struct_identity _identity; +}; +static const struct_field_info prospect_options_fields[] = { + { struct_field_info::PRIMITIVE, "help", offsetof(prospect_options, help), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "hidden", offsetof(prospect_options, hidden), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "value", offsetof(prospect_options, value), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "tube", offsetof(prospect_options, tube), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "summary", offsetof(prospect_options, summary), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "liquids", offsetof(prospect_options, liquids), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "layers", offsetof(prospect_options, layers), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "features", offsetof(prospect_options, features), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ores", offsetof(prospect_options, ores), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "gems", offsetof(prospect_options, gems), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "veins", offsetof(prospect_options, veins), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "shrubs", offsetof(prospect_options, shrubs), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "trees", offsetof(prospect_options, trees), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::END } +}; +struct_identity prospect_options::_identity(sizeof(prospect_options), &df::allocator_fn, NULL, "prospect_options", NULL, prospect_options_fields); + struct matdata { const static int invalid_z = -30000; @@ -123,9 +168,9 @@ static void printMatdata(color_ostream &con, const matdata &data, bool only_z = con << std::setw(9) << int(data.count); if(data.lower_z != data.upper_z) - con <<" Z:" << std::setw(4) << data.lower_z << ".." << data.upper_z << std::endl; + con <<" Z:" << std::setw(4) << data.lower_z << ".." << data.upper_z << std::endl; else - con <<" Z:" << std::setw(4) << data.lower_z << std::endl; + con <<" Z:" << std::setw(4) << data.lower_z << std::endl; } static int getValue(const df::inorganic_raw &info) @@ -139,7 +184,7 @@ static int getValue(const df::plant_raw &info) } template class P> -void printMats(color_ostream &con, MatMap &mat, std::vector &materials, bool show_value) +void printMats(color_ostream &con, MatMap &mat, std::vector &materials, const prospect_options &options) { unsigned int total = 0; MatSorter sorting_vector; @@ -161,7 +206,7 @@ void printMats(color_ostream &con, MatMap &mat, std::vector &materials, bool T* mat = materials[it->first]; // Somewhat of a hack, but it works because df::inorganic_raw and df::plant_raw both have a field named "id" con << std::setw(25) << mat->id << " : "; - if (show_value) + if (options.value) con << std::setw(3) << getValue(*mat) << " : "; printMatdata(con, it->second); total += it->second.count; @@ -171,7 +216,7 @@ void printMats(color_ostream &con, MatMap &mat, std::vector &materials, bool } void printVeins(color_ostream &con, MatMap &mat_map, - DFHack::Materials* mats, bool show_value) + const prospect_options &options) { MatMap ores; MatMap gems; @@ -194,14 +239,20 @@ void printVeins(color_ostream &con, MatMap &mat_map, rest[kv.first] = kv.second; } - con << "Ores:" << std::endl; - printMats(con, ores, world->raws.inorganics, show_value); + if (options.ores) { + con << "Ores:" << std::endl; + printMats(con, ores, world->raws.inorganics, options); + } - con << "Gems:" << std::endl; - printMats(con, gems, world->raws.inorganics, show_value); + if (options.gems) { + con << "Gems:" << std::endl; + printMats(con, gems, world->raws.inorganics, options); + } - con << "Other vein stone:" << std::endl; - printMats(con, rest, world->raws.inorganics, show_value); + if (options.veins) { + con << "Other vein stone:" << std::endl; + printMats(con, rest, world->raws.inorganics, options); + } } command_result prospector (color_ostream &out, vector & parameters); @@ -211,18 +262,43 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector ]\n" + "\n" + " Shows a summary of resources that exist on the map. By default,\n" + " only the visible part of the map is scanned. Include the 'all' keyword\n" + " if you want prospect to scan the whole map as if it were revealed.\n" + " Use 'hell' instead of 'all' if you also want to see the Z range of HFS\n" + " tubes in the 'features' report section.\n" + "\n" + "Options:\n" + " -h,--help\n" + " Shows this help text.\n" + " -s,--show \n" + " Shows only the named comma-separated list of report sections.\n" + " Report section names are: summary, liquids, layers, features, ores,\n" + " gems, veins, shrubs, and trees. If run during pre-embark, only the\n" + " layers, ores, gems, and veins report sections are available.\n" + " -v,--values\n" + " Includes material value in the output. Most useful for the 'gems'\n" + " report section.\n" + "\n" + "Examples:\n" + " prospect all\n" + " Shows the entire report for the entire map.\n" + "\n" + " prospect hell --show layers,ores,veins\n" + " Shows only the layers, ores, and other vein stone report sections,\n" + " and includes information on HFS tubes when a fort is loaded.\n" + "\n" + " prospect all -sores\n" + " Show only information about ores for the pre-embark or fortress map\n" + " report.\n" + "\n" + "Pre-embark estimate:\n" + " If called during the embark selection screen, displays a rough\n" + " estimate of layer stone availability. If the 'all' keyword is\n" + " specified, also estimates ores, gems, and other vein material. The\n" + " estimate covers all tiles of the embark rectangle.\n" )); return CR_OK; } @@ -522,8 +598,9 @@ bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &laye return true; } -static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen, - bool showHidden, bool showValue) +static command_result embark_prospector(color_ostream &out, + df::viewscreen_choose_start_sitest *screen, + const prospect_options &options) { if (!world || !world->world_data) { @@ -549,12 +626,6 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos // Compute biomes std::map biomes; - /*if (screen->biome_highlighted) - { - out.print("Processing one embark tile of biome F%d.\n\n", screen->biome_idx+1); - biomes[screen->biome_rgn[screen->biome_idx]]++; - }*/ - for (int x = screen->location.embark_pos_min.x; x <= 15 && x <= screen->location.embark_pos_max.x; x++) { for (int y = screen->location.embark_pos_min.y; y <= 15 && y <= screen->location.embark_pos_max.y; y++) @@ -570,12 +641,14 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos } // Print the report - out << "Layer materials:" << std::endl; - printMats(out, layerMats, world->raws.inorganics, showValue); + if (options.layers) { + out << "Layer materials:" << std::endl; + printMats(out, layerMats, world->raws.inorganics, options); + } - if (showHidden) { + if (options.hidden) { DFHack::Materials *mats = Core::getInstance().getMaterials(); - printVeins(out, veinMats, mats, showValue); + printVeins(out, veinMats, options); mats->Finish(); } @@ -587,40 +660,8 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos return CR_OK; } -command_result prospector (color_ostream &con, vector & parameters) -{ - bool showHidden = false; - bool showPlants = true; - bool showSlade = true; - bool showTemple = true; - bool showValue = false; - bool showTube = false; - - for(size_t i = 0; i < parameters.size();i++) - { - if (parameters[i] == "all") - { - showHidden = true; - } - else if (parameters[i] == "value") - { - showValue = true; - } - else if (parameters[i] == "hell") - { - showHidden = showTube = true; - } - else - return CR_WRONG_USAGE; - } - - CoreSuspender suspend; - - // Embark screen active: estimate using world geology data - auto screen = Gui::getViewscreenByType(0); - if (screen) - return embark_prospector(con, screen, showHidden, showValue); - +static command_result map_prospector(color_ostream &con, + const prospect_options &options) { if (!Maps::IsValid()) { con.printerr("Map is not available!\n"); @@ -636,7 +677,6 @@ command_result prospector (color_ostream &con, vector & parameters) DFHack::t_feature blockFeatureGlobal; DFHack::t_feature blockFeatureLocal; - bool hasAquifer = false; bool hasDemonTemple = false; bool hasLair = false; MatMap baseMats; @@ -680,7 +720,7 @@ command_result prospector (color_ostream &con, vector & parameters) df::tile_occupancy occ = b->OccupancyAt(coord); // Skip hidden tiles - if (!showHidden && des.bits.hidden) + if (!options.hidden && des.bits.hidden) { continue; } @@ -688,7 +728,6 @@ command_result prospector (color_ostream &con, vector & parameters) // Check for aquifer if (des.bits.water_table) { - hasAquifer = true; aquiferTiles.add(global_z); } @@ -752,14 +791,13 @@ command_result prospector (color_ostream &con, vector & parameters) { veinMats[blockFeatureLocal.sub_material].add(global_z); } - else if (showTemple - && blockFeatureLocal.type == feature_type::deep_surface_portal) + else if (blockFeatureLocal.type == feature_type::deep_surface_portal) { hasDemonTemple = true; } } - if (showSlade && blockFeatureGlobal.type != -1 && des.bits.feature_global + if (blockFeatureGlobal.type != -1 && des.bits.feature_global && blockFeatureGlobal.type == feature_type::underworld_from_layer && blockFeatureGlobal.main_material == 0) // stone { @@ -777,7 +815,7 @@ command_result prospector (color_ostream &con, vector & parameters) // Check plants this way, as the other way wasn't getting them all // and we can check visibility more easily here - if (showPlants) + if (options.shrubs) { auto block = Maps::getBlockColumn(b_x,b_y); vector *plants = block ? &block->plants : NULL; @@ -790,7 +828,7 @@ command_result prospector (color_ostream &con, vector & parameters) continue; df::coord2d loc(plant.pos.x, plant.pos.y); loc = loc % 16; - if (showHidden || !b->DesignationAt(loc).bits.hidden) + if (options.hidden || !b->DesignationAt(loc).bits.hidden) { if(plant.flags.bits.is_shrub) plantMats[plant.material].add(global_z); @@ -810,15 +848,18 @@ command_result prospector (color_ostream &con, vector & parameters) MatMap::const_iterator it; - con << "Base materials:" << std::endl; - for (it = baseMats.begin(); it != baseMats.end(); ++it) - { - con << std::setw(25) << ENUM_KEY_STR(tiletype_material,(df::tiletype_material)it->first) << " : " << it->second.count << std::endl; + if (options.summary) { + con << "Base materials:" << std::endl; + for (it = baseMats.begin(); it != baseMats.end(); ++it) + { + con << std::setw(25) << ENUM_KEY_STR(tiletype_material,(df::tiletype_material)it->first) << " : " << it->second.count << std::endl; + } + con << std::endl; } - if (liquidWater.count || liquidMagma.count) + if (options.liquids && (liquidWater.count || liquidMagma.count)) { - con << std::endl << "Liquids:" << std::endl; + con << "Liquids:" << std::endl; if (liquidWater.count) { con << std::setw(25) << "WATER" << " : "; @@ -829,51 +870,108 @@ command_result prospector (color_ostream &con, vector & parameters) con << std::setw(25) << "MAGMA" << " : "; printMatdata(con, liquidMagma); } + con << std::endl; } - con << std::endl << "Layer materials:" << std::endl; - printMats(con, layerMats, world->raws.inorganics, showValue); - - printVeins(con, veinMats, mats, showValue); - - if (showPlants) - { - con << "Shrubs:" << std::endl; - printMats(con, plantMats, world->raws.plants.all, showValue); - con << "Wood in trees:" << std::endl; - printMats(con, treeMats, world->raws.plants.all, showValue); + if (options.layers) { + con << "Layer materials:" << std::endl; + printMats(con, layerMats, world->raws.inorganics, options); } - if (hasAquifer) - { - con << "Has aquifer"; + if (options.features) { + con << "Features:" << std::endl; + + bool hasFeature = false; if (aquiferTiles.count) { - con << " : "; + con << std::setw(25) << "Has aquifer" << " : "; + if (options.value) + con << " "; printMatdata(con, aquiferTiles); + hasFeature = true; } - else - con << std::endl; - } - if (showTube && tubeTiles.count) - { - con << "Has HFS tubes : "; - printMatdata(con, tubeTiles); + if (options.tube && tubeTiles.count) + { + con << std::setw(25) << "Has HFS tubes" << " : "; + if (options.value) + con << " "; + printMatdata(con, tubeTiles, true); + hasFeature = true; + } + + if (hasDemonTemple) + { + con << std::setw(25) << "Has demon temple" << std::endl; + hasFeature = true; + } + + if (hasLair) + { + con << std::setw(25) << "Has lair" << std::endl; + hasFeature = true; + } + + if (!hasFeature) + con << std::setw(25) << "None" << std::endl; + + con << std::endl; } - if (hasDemonTemple) - { - con << "Has demon temple" << std::endl; + printVeins(con, veinMats, options); + + if (options.shrubs) { + con << "Shrubs:" << std::endl; + printMats(con, plantMats, world->raws.plants.all, options); } - if (hasLair) - { - con << "Has lair" << std::endl; + if (options.trees) { + con << "Wood in trees:" << std::endl; + printMats(con, treeMats, world->raws.plants.all, options); } // Cleanup mats->Finish(); - con << std::endl; + return CR_OK; } + +static bool get_options(color_ostream &out, + prospect_options &opts, + const vector ¶meters) +{ + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, parameters.size() + 2) || + !Lua::PushModulePublic( + out, L, "plugins.prospector", "parse_commandline")) { + out.printerr("Failed to load prospector Lua code\n"); + return false; + } + + Lua::Push(L, &opts); + + for (const string ¶m : parameters) + Lua::Push(L, param); + + if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) + return false; + + return true; +} + +command_result prospector(color_ostream &con, vector & parameters) +{ + CoreSuspender suspend; + + prospect_options options; + if (!get_options(con, options, parameters) || options.help) + return CR_WRONG_USAGE; + + // Embark screen active: estimate using world geology data + auto screen = Gui::getViewscreenByType(0); + return screen ? + embark_prospector(con, screen, options) : + map_prospector(con, options); +} From e0d37a31ae9fab5bec8fb06d977d45f99785f70a Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 6 Jul 2022 07:03:29 -0700 Subject: [PATCH 169/854] Make the manager orders library available by default (#2233) * move orders out of examples directory * install orders into library dir * read orders from new library dir * update documentation * update dreamfort references to orders import * update changelog * ignore json files in pre-commit --- .pre-commit-config.yaml | 2 +- data/CMakeLists.txt | 3 + data/blueprints/library/dreamfort.csv | 22 +++-- data/{examples => }/orders/basic.json | 0 data/{examples => }/orders/furnace.json | 0 data/{examples => }/orders/glassstock.json | 0 data/{examples => }/orders/military.json | 0 data/{examples => }/orders/rockstock.json | 0 data/{examples => }/orders/smelting.json | 0 docs/Plugins.rst | 98 +++++++++++++++++++++ docs/changelog.txt | 2 + docs/guides/examples-guide.rst | 99 ---------------------- library/modules/Filesystem.cpp | 9 +- plugins/orders.cpp | 41 ++++++++- 14 files changed, 160 insertions(+), 116 deletions(-) rename data/{examples => }/orders/basic.json (100%) rename data/{examples => }/orders/furnace.json (100%) rename data/{examples => }/orders/glassstock.json (100%) rename data/{examples => }/orders/military.json (100%) rename data/{examples => }/orders/rockstock.json (100%) rename data/{examples => }/orders/smelting.json (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4e7a4f48b..25601d8b8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,4 +41,4 @@ repos: entry: python3 ci/authors-rst.py files: docs/Authors\.rst pass_filenames: false -exclude: '^(depends/|data/examples/.*\.json$|.*\.diff$)' +exclude: '^(depends/|data/.*\.json$|.*\.diff$)' diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 53e88fed9..72df7272a 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -1,6 +1,9 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/quickfort/ DESTINATION "${DFHACK_DATA_DESTINATION}/data/quickfort") +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/orders/ + DESTINATION dfhack-config/orders/library) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/ DESTINATION "${DFHACK_DATA_DESTINATION}/examples") diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index a00a00e9e..ec1a05c51 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -31,7 +31,7 @@ "Other DFHack commands also work very well with Dreamfort, such as autofarm, autonestbox, prioritize, seedwatch, tailor, and, of course, buildingplan. An init file that gets everything configured for you is distributed with DFHack as hack/examples/init/onMapLoad_dreamfort.init." Put that file in your Dwarf Fortress directory -- the same directory that has dfhack.init. "" -"Also copy the files in hack/examples/orders/ to dfhack-config/orders/ and the files in hack/examples/professions/ to professions/. We'll be using these files later. See https://docs.dfhack.org/en/stable/docs/guides/examples-guide.html for more information, including suggestions on how many dwarves of each profession you are likely to need at each stage of fort maturity." +"Also copy the files in hack/examples/professions/ to professions/. We'll be using these files later. See https://docs.dfhack.org/en/stable/docs/guides/examples-guide.html for more information, including suggestions on how many dwarves of each profession you are likely to need at each stage of fort maturity." "" "Once you have your starting surface workshops up and running, you might want to configure buildingplan (in its global settings, accessible from any building placement screen, e.g.: b-a-G) to only use blocks for constructions so it won't use your precious wood, boulders, and bars to build floors and walls. If you bring at least 7 blocks with you on embark, you can even set this in your onMapLoad.init file like this:" on-new-fortress buildingplan set boulders false; buildingplan set logs false @@ -50,7 +50,6 @@ interactively." "" -- Preparation (before you embark!) -- Copy hack/examples/init/onMapLoad_dreamfort.init to your DF directory -Copy the fort automation orders from hack/examples/orders/*.json to the dfhack-config/orders/ directory Optionally copy the premade profession definitions from hack/examples/professions/ to the professions/ directory Optionally copy the premade Dreamfort embark profile from the online spreadsheets to the data/init/embark_profiles.txt file "" @@ -75,7 +74,7 @@ quickfort run library/dreamfort.csv -n /industry2,# Run when the industry level prioritize ConstructBuilding,# To get those workshops up and running ASAP. You may have to run this several times as the materials for the building construction jobs become ready. quickfort run library/dreamfort.csv -n /surface4,"# Run after the walls and floors are built on the surface. Even if /surface3 is finished before you run /industry2, though, wait until after /industry2 to run this blueprint so that surface walls, floors, and roofing don't prevent your workshops from being built (due to lack of blocks)." "quickfort orders,run library/dreamfort.csv -n /services2",# Run when the services levels have been dug out. Feel free to remove the orders for the ropes if you already brought them with you. -orders import basic,"# Run after the first migration wave, so you have dorfs to do all the basic tasks. Note that this is the ""orders"" plugin, not the ""quickfort orders"" command." +orders import library/basic,"# Run after the first migration wave, so you have dorfs to do all the basic tasks. Note that this is the ""orders"" plugin, not the ""quickfort orders"" command." "quickfort orders,run library/dreamfort.csv -n /surface5","# Run when all marked trees on the surface are chopped down and walls and floors have been constructed, including the roof section over the future barracks." prioritize ConstructBuilding,# Run when you see the bridges ready to be built so the busy masons come and build them. "quickfort orders,run library/dreamfort.csv -n /surface6",# Run when at least the beehives and weapon rack are constructed and you have linked all levers to their respective bridges. @@ -86,7 +85,7 @@ prioritize ConstructBuilding,# Run when you see the bridges ready to be built so "Also consider bringing magma up to your services level so you can replace the forge and furnaces on your industry level with more powerful magma versions. This is especially important if your embark has insufficient trees to convert into charcoal. Keep in mind that moving magma is a tricky process and can take a long time. Don't forget to continue making progress through the checklist! If you choose to use magma, I suggest getting it in place before importing the military and smelting automation orders since they make heavy use of furnaces and forges." "" -- Mature fort (third migration wave onward) -- -orders import furnace,# Automated production of basic furnace-related items. Don't forget to create a sand collection zone (or remove the sand- and glass-related orders if you have no sand). +orders import library/furnace,# Automated production of basic furnace-related items. Don't forget to create a sand collection zone (or remove the sand- and glass-related orders if you have no sand). "quickfort orders,run library/dreamfort.csv -n /suites2",# Run when the suites level has been dug out. "quickfort orders,run library/dreamfort.csv -n /surface8","# Run if/when you need longer trap corridors on the surface for larger sieges, anytime after you run /surface7." "quickfort orders,run library/dreamfort.csv -n /apartments2",# Run when the first apartment level has been dug out. @@ -95,11 +94,11 @@ orders import furnace,# Automated production of basic furnace-related items. Don "quickfort orders,run library/dreamfort.csv -n ""/guildhall3, /guildhall4""",# Optionally run after /guildhall2 to build default furnishings and declare a library and temple. "quickfort orders,run library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed on the first apartments level. "quickfort orders,run library/dreamfort.csv -n /farming4",# Run once you have a cache of potash. -orders import military,# Automated production of military equipment. Turn on automelt in the meltables piles on the industry level to automatically upgrade all metal military equipment to masterwork quality. These orders are optional if you are not using a military. -orders import smelting,# Automated production of all types of metal bars. +orders import library/military,# Automated production of military equipment. Turn on automelt in the meltables piles on the industry level to automatically upgrade all metal military equipment to masterwork quality. These orders are optional if you are not using a military. +orders import library/smelting,# Automated production of all types of metal bars. "quickfort orders,run library/dreamfort.csv -n /services4","# Run when you need a jail, anytime after the restraints are placed from /services3." -orders import rockstock,# Maintains a small stock of all types of rock furniture. -orders import glassstock,# Maintains a small stock of all types of glass furniture and parts (only import if you have sand). +orders import library/rockstock,# Maintains a small stock of all types of rock furniture. +orders import library/glassstock,# Maintains a small stock of all types of glass furniture and parts (only import if you have sand). "" -- Repeat for each remaining apartments level as needed -- "quickfort orders,run library/dreamfort.csv -n /apartments2",# Run when the apartment level has been dug out. @@ -1830,7 +1829,6 @@ Workshops: Manual steps you have to take: - Assign minecarts to your quantum stockpile hauling routes "- Give from the ""Goods"" quantum stockpile to the jugs, pots, and bags stockpiles on the farming level" -- Copy the fort automation manager orders (the .json files) from hack/examples/orders/ and put them in your dfhack-config/orders/ directory. "" Optional manual steps you can take: - Restrict the Mechanic's workshop to only allow skilled workers so unskilled trap-resetters won't be tasked to build mechanisms. @@ -1846,11 +1844,11 @@ Industry Walkthrough: "" "3) Once the area is dug out, run /industry2. Remember to assign minecarts to to your quantum stockpile hauling routes, and if the farming level is already built, give from the ""Goods"" quantum stockpile (the one on the left) to the jugs, pots, and bags stockpiles on the farming level." "" -"4) Once you have enough dwarves to do maintenance tasks (that is, after the first or second migration wave), run ""orders import basic"" to use the provided basic.json to take care of your fort's basic needs, such as food, booze, and raw material processing." +"4) Once you have enough dwarves to do maintenance tasks (that is, after the first or second migration wave), run ""orders import library/basic"" to use the provided basic.json to take care of your fort's basic needs, such as food, booze, and raw material processing." "" "5) If you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt. If you don't have a high density of trees to make into charcoal, though, be sure to route magma to the level beneath this one and replace the forge and furnaces with magma equivalents." "" -"6) Once you have magma furnaces (or abundant fuel) and more dwarves, run ""orders import furnace"", ""orders import military"", and ""orders import smelting"" to import the remaining fort automation orders. The military orders are optional if you are not planning to have a military, of course." +"6) Once you have magma furnaces (or abundant fuel) and more dwarves, run ""orders import library/furnace"", ""orders import library/military"", and ""orders import library/smelting"" to import the remaining fort automation orders. The military orders are optional if you are not planning to have a military, of course." "" "7) At any time, feel free to build extra workshops or designate custom stockpiles in the unused space in the top and bottom right. The space is there for you to use!" "#dig label(industry1) start(18; 18; central stairs) message(Once the area is dug out, continue with /industry2.)" @@ -1969,7 +1967,7 @@ query/industry_query - assign minecarts to to your quantum stockpile hauling routes (use ""assign-minecarts all"") - if the farming level is already built, give from the ""Goods"" quantum stockpile to the jugs, pots, and bags stockpiles on the farming level - if you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt -- once you have enough dwarves, run ""orders import basic"" to automate your fort's basic needs (see /industry_help for more info on this file) +- once you have enough dwarves, run ""orders import library/basic"" to automate your fort's basic needs (see /industry_help for more info on this file) - optionally, restrict the labors for your Craftsdwarf's and Mechanic's workshops as per the guidance in /industry_help)" diff --git a/data/examples/orders/basic.json b/data/orders/basic.json similarity index 100% rename from data/examples/orders/basic.json rename to data/orders/basic.json diff --git a/data/examples/orders/furnace.json b/data/orders/furnace.json similarity index 100% rename from data/examples/orders/furnace.json rename to data/orders/furnace.json diff --git a/data/examples/orders/glassstock.json b/data/orders/glassstock.json similarity index 100% rename from data/examples/orders/glassstock.json rename to data/orders/glassstock.json diff --git a/data/examples/orders/military.json b/data/orders/military.json similarity index 100% rename from data/examples/orders/military.json rename to data/orders/military.json diff --git a/data/examples/orders/rockstock.json b/data/orders/rockstock.json similarity index 100% rename from data/examples/orders/rockstock.json rename to data/orders/rockstock.json diff --git a/data/examples/orders/smelting.json b/data/orders/smelting.json similarity index 100% rename from data/examples/orders/smelting.json rename to data/orders/smelting.json diff --git a/docs/Plugins.rst b/docs/Plugins.rst index e5c662ffa..c73bfab3a 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1129,6 +1129,7 @@ A plugin for manipulating manager orders. Subcommands: +:list: Shows the list of previously exported orders, including the orders library. :export NAME: Exports the current list of manager orders to a file named ``dfhack-config/orders/NAME.json``. :import NAME: Imports manager orders from a file named ``dfhack-config/orders/NAME.json``. :clear: Deletes all manager orders in the current embark. @@ -1141,6 +1142,103 @@ your ``onMapLoad.init`` file:: repeat -name orders-sort -time 1 -timeUnits days -command [ orders sort ] + +The orders library +------------------ + +DFHack comes with a library of useful manager orders that are ready for import: + +:source:`basic.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection of orders handles basic fort necessities: + +- prepared meals and food products (and by-products like oil) +- booze/mead +- thread/cloth/dye +- pots/jugs/buckets/mugs +- bags of leather, cloth, silk, and yarn +- crafts and totems from otherwise unusable by-products +- mechanisms/cages +- splints/crutches +- lye/soap +- ash/potash +- beds/wheelbarrows/minecarts +- scrolls + +You should import it as soon as you have enough dwarves to perform the tasks. +Right after the first migration wave is usually a good time. + +:source:`furnace.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection creates basic items that require heat. It is separated out from +``basic.json`` to give players the opportunity to set up magma furnaces first in +order to save resources. It handles: + +- charcoal (including smelting of bituminous coal and lignite) +- pearlash +- sand +- green/clear/crystal glass +- adamantine processing +- item melting + +Orders are missing for plaster powder until DF :bug:`11803` is fixed. + +:source:`military.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection adds high-volume smelting jobs for military-grade metal ores and +produces weapons and armor: + +- leather backpacks/waterskins/cloaks/quivers/armor +- bone/wooden bolts +- smelting for platinum, silver, steel, bronze, bismuth bronze, and copper (and + their dependencies) +- bronze/bismuth bronze/copper bolts +- platinum/silver/steel/iron/bismuth bronze/bronze/copper weapons and armor, + with checks to ensure only the best available materials are being used + +If you set a stockpile to take weapons and armor of less than masterwork quality +and turn on `automelt` (like what `dreamfort` provides on its industry level), +these orders will automatically upgrade your military equipment to masterwork. +Make sure you have a lot of fuel (or magma forges and furnaces) before you turn +``automelt`` on, though! + +This file should only be imported, of course, if you need to equip a military. + +:source:`smelting.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection adds smelting jobs for all ores. It includes handling the ores +already managed by ``military.json``, but has lower limits. This ensures all +ores will be covered if a player imports ``smelting`` but not ``military``, but +the higher-volume ``military`` orders will take priority if both are imported. + +:source:`rockstock.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection of orders keeps a small stock of all types of rock furniture. +This allows you to do ad-hoc furnishings of guildhalls, libraries, temples, or +other rooms with `buildingplan` and your masons will make sure there is always +stock on hand to fulfill the plans. + +:source:`glassstock.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Similar to ``rockstock`` above, this collection keeps a small stock of all types +of glass furniture. If you have a functioning glass industry, this is more +sustainable than ``rockstock`` since you can never run out of sand. If you have +plenty of rock and just want the variety, you can import both ``rockstock`` and +``glassstock`` to get a mixture of rock and glass furnishings in your fort. + +There are a few items that ``glassstock`` produces that ``rockstock`` does not, +since there are some items that can not be made out of rock, for example: + +- tubes and corkscrews for building magma-safe screw pumps +- windows +- terrariums (as an alternative to wooden cages) + .. _seedwatch: seedwatch diff --git a/docs/changelog.txt b/docs/changelog.txt index 0c1422306..ccc94701c 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. + +- `orders`: added useful library of manager orders. see them with ``orders list`` and import them with, for example, ``orders import library/basic`` - `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. ## Documentation diff --git a/docs/guides/examples-guide.rst b/docs/guides/examples-guide.rst index 8ea463d8c..58446506e 100644 --- a/docs/guides/examples-guide.rst +++ b/docs/guides/examples-guide.rst @@ -48,105 +48,6 @@ it is useful (and customizable) for any fort. It includes the following config: settings won't be overridden. - Enables `automelt`, `tailor`, `zone`, `nestboxes`, and `autonestbox`. -The ``orders/`` subfolder -------------------------- - -The :source:`orders/ ` subfolder contains manager orders -that, along with the ``onMapLoad_dreamfort.init`` file above, allow a fort to be -self-sustaining. Copy them to your ``dfhack-config/orders/`` folder and import -as required with the `orders` DFHack plugin. - -:source:`basic.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection of orders handles basic fort necessities: - -- prepared meals and food products (and by-products like oil) -- booze/mead -- thread/cloth/dye -- pots/jugs/buckets/mugs -- bags of leather, cloth, silk, and yarn -- crafts and totems from otherwise unusable by-products -- mechanisms/cages -- splints/crutches -- lye/soap -- ash/potash -- beds/wheelbarrows/minecarts -- scrolls - -You should import it as soon as you have enough dwarves to perform the tasks. -Right after the first migration wave is usually a good time. - -:source:`furnace.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection creates basic items that require heat. It is separated out from -``basic.json`` to give players the opportunity to set up magma furnaces first in -order to save resources. It handles: - -- charcoal (including smelting of bituminous coal and lignite) -- pearlash -- sand -- green/clear/crystal glass -- adamantine processing -- item melting - -Orders are missing for plaster powder until DF :bug:`11803` is fixed. - -:source:`military.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection adds high-volume smelting jobs for military-grade metal ores and -produces weapons and armor: - -- leather backpacks/waterskins/cloaks/quivers/armor -- bone/wooden bolts -- smelting for platinum, silver, steel, bronze, bismuth bronze, and copper (and - their dependencies) -- bronze/bismuth bronze/copper bolts -- platinum/silver/steel/iron/bismuth bronze/bronze/copper weapons and armor, - with checks to ensure only the best available materials are being used - -If you set a stockpile to take weapons and armor of less than masterwork quality -and turn on `automelt` (like what `dreamfort` provides on its industry level), -these orders will automatically upgrade your military equipment to masterwork. -Make sure you have a lot of fuel (or magma forges and furnaces) before you turn -``automelt`` on, though! - -This file should only be imported, of course, if you need to equip a military. - -:source:`smelting.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection adds smelting jobs for all ores. It includes handling the ores -already managed by ``military.json``, but has lower limits. This ensures all -ores will be covered if a player imports ``smelting`` but not ``military``, but -the higher-volume ``military`` orders will take priority if both are imported. - -:source:`rockstock.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection of orders keeps a small stock of all types of rock furniture. -This allows you to do ad-hoc furnishings of guildhalls, libraries, temples, or -other rooms with `buildingplan` and your masons will make sure there is always -stock on hand to fulfill the plans. - -:source:`glassstock.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Similar to ``rockstock`` above, this collection keeps a small stock of all types -of glass furniture. If you have a functioning glass industry, this is more -sustainable than ``rockstock`` since you can never run out of sand. If you have -plenty of rock and just want the variety, you can import both ``rockstock`` and -``glassstock`` to get a mixture of rock and glass furnishings in your fort. - -There are a few items that ``glassstock`` produces that ``rockstock`` does not, -since there are some items that can not be made out of rock, for example: - -- tubes and corkscrews for building magma-safe screw pumps -- windows -- terrariums (as an alternative to wooden cages) - The ``professions/`` subfolder ------------------------------ diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index e0d0bc8c2..a182ac062 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -246,6 +246,7 @@ static int listdir_recursive_impl (std::string prefix, std::string path, int err = Filesystem::listdir(prefixed_path, curdir_files); if (err) return err; + bool out_of_depth = false; for (auto file = curdir_files.begin(); file != curdir_files.end(); ++file) { if (*file == "." || *file == "..") @@ -254,6 +255,12 @@ static int listdir_recursive_impl (std::string prefix, std::string path, std::string path_file = path + *file; if (Filesystem::isdir(prefixed_file)) { + if (depth == 0) + { + out_of_depth = true; + continue; + } + files.insert(std::pair(include_prefix ? prefixed_file : path_file, true)); err = listdir_recursive_impl(prefix, path_file + "/", files, depth - 1, include_prefix); if (err) @@ -264,7 +271,7 @@ static int listdir_recursive_impl (std::string prefix, std::string path, files.insert(std::pair(include_prefix ? prefixed_file : path_file, false)); } } - return 0; + return out_of_depth ? -1 : 0; } int Filesystem::listdir_recursive (std::string dir, std::map &files, diff --git a/plugins/orders.cpp b/plugins/orders.cpp index fb678ca6d..9e1bf3e1a 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -41,6 +41,7 @@ DFHACK_PLUGIN("orders"); REQUIRE_GLOBAL(world); static const std::string ORDERS_DIR = "dfhack-config/orders"; +static const std::string ORDERS_LIBRARY_DIR = "dfhack-config/orders/library"; static command_result orders_command(color_ostream & out, std::vector & parameters); @@ -53,7 +54,7 @@ DFhackCExport command_result plugin_init(color_ostream & out, std::vector files; + if (0 < Filesystem::listdir_recursive(ORDERS_LIBRARY_DIR, files, 0, false)) { + // if the library directory doesn't exist, just skip it + return; + } + + if (files.empty()) { + // if no files in the library directory, just skip it + return; + } + + for (auto it : files) + { + if (it.second) + continue; // skip directories + std::string name = it.first; + if (name.length() <= 5 || name.rfind(".json") != name.length() - 5) + continue; // skip non-.json files + name.resize(name.length() - 5); + out << "library/" << name << std::endl; + } +} + static command_result orders_list_command(color_ostream & out) { // use listdir_recursive instead of listdir even though orders doesn't @@ -150,6 +175,8 @@ static command_result orders_list_command(color_ostream & out) out << name << std::endl; } + list_library(out); + return CR_OK; } @@ -889,12 +916,20 @@ static command_result orders_import(color_ostream &out, Json::Value &orders) static command_result orders_import_command(color_ostream & out, const std::string & name) { - if (!is_safe_filename(out, name)) + std::string fname = name; + bool is_library = false; + if (0 == name.find("library/")) { + is_library = true; + fname = name.substr(8); + } + + if (!is_safe_filename(out, fname)) { return CR_WRONG_USAGE; } - const std::string filename(ORDERS_DIR + "/" + name + ".json"); + const std::string filename((is_library ? ORDERS_LIBRARY_DIR : ORDERS_DIR) + + "/" + fname + ".json"); Json::Value orders; { From 9f44fd3f723e887587ebe3c54a20a51dbe0de059 Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 6 Jul 2022 07:21:26 -0700 Subject: [PATCH 170/854] [manipulator] add the professions library (#2234) * move professions out of the examples folder * install professions into professions/library * guard unguarded header from multiple inclusion * load and display library professions * update changelog * move example professions docs from examples guide * update dreamfort documentation * note that professions folder has changed * Fix bad merge --- data/CMakeLists.txt | 3 + data/blueprints/library/dreamfort.csv | 8 +- data/{examples => }/professions/Chef | 2 +- data/{examples => }/professions/Craftsdwarf | 2 +- data/{examples => }/professions/Doctor | 2 +- data/{examples => }/professions/Farmer | 2 +- data/{examples => }/professions/Fisherdwarf | 2 +- data/{examples => }/professions/Hauler | 2 +- data/{examples => }/professions/Laborer | 2 +- data/{examples => }/professions/Marksdwarf | 2 +- data/{examples => }/professions/Mason | 2 +- data/{examples => }/professions/Meleedwarf | 2 +- data/{examples => }/professions/Migrant | 2 +- data/{examples => }/professions/Miner | 2 +- data/{examples => }/professions/Outdoorsdwarf | 2 +- data/{examples => }/professions/Smith | 2 +- data/{examples => }/professions/StartManager | 2 +- data/{examples => }/professions/Tailor | 2 +- docs/Plugins.rst | 117 ++++++++++++++++- docs/changelog.txt | 4 +- docs/guides/examples-guide.rst | 122 ------------------ plugins/listcolumn.h | 2 + plugins/manipulator.cpp | 59 ++++++--- 23 files changed, 183 insertions(+), 164 deletions(-) rename data/{examples => }/professions/Chef (92%) rename data/{examples => }/professions/Craftsdwarf (92%) rename data/{examples => }/professions/Doctor (93%) rename data/{examples => }/professions/Farmer (83%) rename data/{examples => }/professions/Fisherdwarf (90%) rename data/{examples => }/professions/Hauler (92%) rename data/{examples => }/professions/Laborer (92%) rename data/{examples => }/professions/Marksdwarf (89%) rename data/{examples => }/professions/Mason (92%) rename data/{examples => }/professions/Meleedwarf (90%) rename data/{examples => }/professions/Migrant (92%) rename data/{examples => }/professions/Miner (66%) rename data/{examples => }/professions/Outdoorsdwarf (92%) rename data/{examples => }/professions/Smith (92%) rename data/{examples => }/professions/StartManager (96%) rename data/{examples => }/professions/Tailor (91%) diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 72df7272a..26befdb34 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -7,6 +7,9 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/orders/ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/ DESTINATION "${DFHACK_DATA_DESTINATION}/examples") +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/professions/ + DESTINATION dfhack-config/professions/library) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/blueprints/ DESTINATION blueprints FILES_MATCHING PATTERN "*" diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index ec1a05c51..ff0b8b4fc 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -29,9 +29,9 @@ "Dreamfort works best at an embark site that is flat and has at least one soil layer. New players should avoid embarks with aquifers if they are not prepared to deal with them. Bring picks for mining, an axe for woodcutting, and an anvil for a forge. Bring a few blocks to speed up initial workshop construction as well. That's all you really need, but see the example embark profile in the online spreadsheets for a more complete setup." "" "Other DFHack commands also work very well with Dreamfort, such as autofarm, autonestbox, prioritize, seedwatch, tailor, and, of course, buildingplan. An init file that gets everything configured for you is distributed with DFHack as hack/examples/init/onMapLoad_dreamfort.init." -Put that file in your Dwarf Fortress directory -- the same directory that has dfhack.init. +Put that file in your dfhack-config/init/ directory -- the same directory that has dfhack.init. "" -"Also copy the files in hack/examples/professions/ to professions/. We'll be using these files later. See https://docs.dfhack.org/en/stable/docs/guides/examples-guide.html for more information, including suggestions on how many dwarves of each profession you are likely to need at each stage of fort maturity." +"Also check out https://docs.dfhack.org/en/stable/docs/Plugins.html#professions for more information on the default labor professions that are distributed with DFHack, including suggestions on how many dwarves of each profession you are likely to need at each stage of fort maturity." "" "Once you have your starting surface workshops up and running, you might want to configure buildingplan (in its global settings, accessible from any building placement screen, e.g.: b-a-G) to only use blocks for constructions so it won't use your precious wood, boulders, and bars to build floors and walls. If you bring at least 7 blocks with you on embark, you can even set this in your onMapLoad.init file like this:" on-new-fortress buildingplan set boulders false; buildingplan set logs false @@ -49,9 +49,7 @@ interactively." "Here is the recommended order for Dreamfort commands. You can copy/paste the command lines directly into the DFHack terminal, or, if you prefer, you can run the blueprints in the UI with gui/quickfort. See the walkthroughs (the ""help"" blueprints) for context and details. Also remember to read the messages the blueprints print out after you run them so you don't miss any important manual steps." "" -- Preparation (before you embark!) -- -Copy hack/examples/init/onMapLoad_dreamfort.init to your DF directory -Optionally copy the premade profession definitions from hack/examples/professions/ to the professions/ directory -Optionally copy the premade Dreamfort embark profile from the online spreadsheets to the data/init/embark_profiles.txt file +Copy hack/examples/init/onMapLoad_dreamfort.init to your dfhack-config/init directory inside your DF installation "" -- Set settings and preload initial orders -- quickfort run library/dreamfort.csv -n /setup,# Run before making any manual adjustments to settings! Run the /setup_help blueprint for details on what this blueprint does. diff --git a/data/examples/professions/Chef b/data/professions/Chef similarity index 92% rename from data/examples/professions/Chef rename to data/professions/Chef index 1f777c81a..218e08301 100644 --- a/data/examples/professions/Chef +++ b/data/professions/Chef @@ -1,4 +1,4 @@ -NAME Chef +NAME library/Chef BUTCHER TANNER COOK diff --git a/data/examples/professions/Craftsdwarf b/data/professions/Craftsdwarf similarity index 92% rename from data/examples/professions/Craftsdwarf rename to data/professions/Craftsdwarf index 29ed1ad0d..8c9707f17 100644 --- a/data/examples/professions/Craftsdwarf +++ b/data/professions/Craftsdwarf @@ -1,4 +1,4 @@ -NAME Craftsdwarf +NAME library/Craftsdwarf WOOD_CRAFT STONE_CRAFT BONE_CARVE diff --git a/data/examples/professions/Doctor b/data/professions/Doctor similarity index 93% rename from data/examples/professions/Doctor rename to data/professions/Doctor index 893708947..14959f599 100644 --- a/data/examples/professions/Doctor +++ b/data/professions/Doctor @@ -1,4 +1,4 @@ -NAME Doctor +NAME library/Doctor ANIMALCARE DIAGNOSE SURGERY diff --git a/data/examples/professions/Farmer b/data/professions/Farmer similarity index 83% rename from data/examples/professions/Farmer rename to data/professions/Farmer index 149b3c368..0b2801f4c 100644 --- a/data/examples/professions/Farmer +++ b/data/professions/Farmer @@ -1,4 +1,4 @@ -NAME Farmer +NAME library/Farmer PLANT MILLER BREWER diff --git a/data/examples/professions/Fisherdwarf b/data/professions/Fisherdwarf similarity index 90% rename from data/examples/professions/Fisherdwarf rename to data/professions/Fisherdwarf index 3c369e61d..1b5d7a1a8 100644 --- a/data/examples/professions/Fisherdwarf +++ b/data/professions/Fisherdwarf @@ -1,4 +1,4 @@ -NAME Fisherdwarf +NAME library/Fisherdwarf FISH CLEAN_FISH DISSECT_FISH diff --git a/data/examples/professions/Hauler b/data/professions/Hauler similarity index 92% rename from data/examples/professions/Hauler rename to data/professions/Hauler index a108b1bfd..4fd8b89f8 100644 --- a/data/examples/professions/Hauler +++ b/data/professions/Hauler @@ -1,4 +1,4 @@ -NAME Hauler +NAME library/Hauler FEED_WATER_CIVILIANS SIEGEOPERATE MECHANIC diff --git a/data/examples/professions/Laborer b/data/professions/Laborer similarity index 92% rename from data/examples/professions/Laborer rename to data/professions/Laborer index bca22a302..ccd688428 100644 --- a/data/examples/professions/Laborer +++ b/data/professions/Laborer @@ -1,4 +1,4 @@ -NAME Laborer +NAME library/Laborer SOAP_MAKER BURN_WOOD POTASH_MAKING diff --git a/data/examples/professions/Marksdwarf b/data/professions/Marksdwarf similarity index 89% rename from data/examples/professions/Marksdwarf rename to data/professions/Marksdwarf index 583afd08e..f34d67cd2 100644 --- a/data/examples/professions/Marksdwarf +++ b/data/professions/Marksdwarf @@ -1,4 +1,4 @@ -NAME Marksdwarf +NAME library/Marksdwarf MECHANIC HAUL_STONE HAUL_WOOD diff --git a/data/examples/professions/Mason b/data/professions/Mason similarity index 92% rename from data/examples/professions/Mason rename to data/professions/Mason index 5f996f448..1977d2df5 100644 --- a/data/examples/professions/Mason +++ b/data/professions/Mason @@ -1,4 +1,4 @@ -NAME Mason +NAME library/Mason MASON CUT_GEM ENCRUST_GEM diff --git a/data/examples/professions/Meleedwarf b/data/professions/Meleedwarf similarity index 90% rename from data/examples/professions/Meleedwarf rename to data/professions/Meleedwarf index 8eac5ffd6..6a8338fea 100644 --- a/data/examples/professions/Meleedwarf +++ b/data/professions/Meleedwarf @@ -1,4 +1,4 @@ -NAME Meleedwarf +NAME library/Meleedwarf RECOVER_WOUNDED MECHANIC HAUL_STONE diff --git a/data/examples/professions/Migrant b/data/professions/Migrant similarity index 92% rename from data/examples/professions/Migrant rename to data/professions/Migrant index 59fd70405..23a3eeddb 100644 --- a/data/examples/professions/Migrant +++ b/data/professions/Migrant @@ -1,4 +1,4 @@ -NAME Migrant +NAME library/Migrant FEED_WATER_CIVILIANS SIEGEOPERATE MECHANIC diff --git a/data/examples/professions/Miner b/data/professions/Miner similarity index 66% rename from data/examples/professions/Miner rename to data/professions/Miner index 7be84512d..3170969d9 100644 --- a/data/examples/professions/Miner +++ b/data/professions/Miner @@ -1,4 +1,4 @@ -NAME Miner +NAME library/Miner MINE DETAIL RECOVER_WOUNDED diff --git a/data/examples/professions/Outdoorsdwarf b/data/professions/Outdoorsdwarf similarity index 92% rename from data/examples/professions/Outdoorsdwarf rename to data/professions/Outdoorsdwarf index a3f696419..31dbd2ad8 100644 --- a/data/examples/professions/Outdoorsdwarf +++ b/data/professions/Outdoorsdwarf @@ -1,4 +1,4 @@ -NAME Outdoorsdwarf +NAME library/Outdoorsdwarf CARPENTER BOWYER CUTWOOD diff --git a/data/examples/professions/Smith b/data/professions/Smith similarity index 92% rename from data/examples/professions/Smith rename to data/professions/Smith index f5fe0f982..7809d80e1 100644 --- a/data/examples/professions/Smith +++ b/data/professions/Smith @@ -1,4 +1,4 @@ -NAME Smith +NAME library/Smith FORGE_WEAPON FORGE_ARMOR FORGE_FURNITURE diff --git a/data/examples/professions/StartManager b/data/professions/StartManager similarity index 96% rename from data/examples/professions/StartManager rename to data/professions/StartManager index 751d75cc9..a70c705bf 100644 --- a/data/examples/professions/StartManager +++ b/data/professions/StartManager @@ -1,4 +1,4 @@ -NAME StartManager +NAME library/StartManager CUTWOOD ANIMALCARE DIAGNOSE diff --git a/data/examples/professions/Tailor b/data/professions/Tailor similarity index 91% rename from data/examples/professions/Tailor rename to data/professions/Tailor index 74ac03a93..f7986553a 100644 --- a/data/examples/professions/Tailor +++ b/data/professions/Tailor @@ -1,4 +1,4 @@ -NAME Tailor +NAME library/Tailor DYER LEATHER WEAVER diff --git a/docs/Plugins.rst b/docs/Plugins.rst index c73bfab3a..8acddfc6d 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -3096,8 +3096,121 @@ To apply a profession, either highlight a single dwarf or select multiple with :kbd:`x`, and press :kbd:`p` to select the profession to apply. All labors for the selected dwarves will be reset to the labors of the chosen profession. -Professions are saved as human-readable text files in the "professions" folder -within the DF folder, and can be edited or deleted there. +Professions are saved as human-readable text files in the +``dfhack-config/professions`` folder within the DF folder, and can be edited or +deleted there. + +The professions library +~~~~~~~~~~~~~~~~~~~~~~~ + +The manipulator plugin comes with a library of professions that you can assign +to your dwarves. + +If you'd rather use Dwarf Therapist to manage your labors, it is easy to import +these professions to DT and use them there. Simply assign the professions you +want to import to a dwarf. Once you have assigned a profession to at least one +dwarf, you can select "Import Professions from DF" in the DT "File" menu. The +professions will then be available for use in DT. + +In the charts below, the "At Start" and "Max" columns indicate the approximate +number of dwarves of each profession that you are likely to need at the start of +the game and how many you are likely to need in a mature fort. These are just +approximations. Your playstyle may demand more or fewer of each profession. + +============= ======== ===== ================================================= +Profession At Start Max Description +============= ======== ===== ================================================= +Chef 0 3 Buchery, Tanning, and Cooking. It is important to + focus just a few dwarves on cooking since + well-crafted meals make dwarves very happy. They + are also an excellent trade good. +Craftsdwarf 0 4-6 All labors used at Craftsdwarf's workshops, + Glassmaker's workshops, and kilns. +Doctor 0 2-4 The full suite of medical labors, plus Animal + Caretaking for those using the dwarfvet plugin. +Farmer 1 4 Food- and animal product-related labors. This + profession also has the ``Alchemist`` labor + enabled since they need to focus on food-related + jobs, though you might want to disable + ``Alchemist`` for your first farmer until there + are actual farming duties to perform. +Fisherdwarf 0 0-1 Fishing and fish cleaning. If you assign this + profession to any dwarf, be prepared to be + inundated with fish. Fisherdwarves *never stop + fishing*. Be sure to also run ``prioritize -a + PrepareRawFish ExtractFromRawFish`` or else + caught fish will just be left to rot. +Hauler 0 >20 All hauling labors plus Siege Operating, Mechanic + (so haulers can assist in reloading traps) and + Architecture (so haulers can help build massive + windmill farms and pump stacks). As you + accumulate enough Haulers, you can turn off + hauling labors for other dwarves so they can + focus on their skilled tasks. You may also want + to restrict your Mechanic's workshops to only + skilled mechanics so your haulers don't make + low-quality mechanisms. +Laborer 0 10-12 All labors that don't improve quality with skill, + such as Soapmaking and furnace labors. +Marksdwarf 0 10-30 Similar to Hauler. See the description for + Meleedwarf below for more details. +Mason 2 2-4 Masonry and Gem Cutting/Encrusting. In the early + game, you may need to run "`prioritize` + ConstructBuilding" to get your masons to build + wells and bridges if they are too busy crafting + stone furniture. +Meleedwarf 0 20-50 Similar to Hauler, but without most civilian + labors. This profession is separate from Hauler + so you can find your military dwarves easily. + Meleedwarves and Marksdwarves have Mechanics and + hauling labors enabled so you can temporarily + deactivate your military after sieges and allow + your military dwarves to help clean up. +Migrant 0 0 You can assign this profession to new migrants + temporarily while you sort them into professions. + Like Marksdwarf and Meleedwarf, the purpose of + this profession is so you can find your new + dwarves more easily. +Miner 2 2-10 Mining and Engraving. This profession also has + the ``Alchemist`` labor enabled, which disables + hauling for those using the `autohauler` plugin. + Once the need for Miners tapers off in the late + game, dwarves with this profession make good + military dwarves, wielding their picks as + weapons. +Outdoorsdwarf 1 2-4 Carpentry, Bowyery, Woodcutting, Animal Training, + Trapping, Plant Gathering, Beekeeping, and Siege + Engineering. +Smith 0 2-4 Smithing labors. You may want to specialize your + Smiths to focus on a single smithing skill to + maximize equipment quality. +StartManager 1 0 All skills not covered by the other starting + professions (Miner, Mason, Outdoorsdwarf, and + Farmer), plus a few overlapping skills to + assist in critical tasks at the beginning of the + game. Individual labors should be turned off as + migrants are assigned more specialized + professions that cover them, and the StartManager + dwarf can eventually convert to some other + profession. +Tailor 0 2 Textile industry labors: Dying, Leatherworking, + Weaving, and Clothesmaking. +============= ======== ===== ================================================= + +A note on autohauler +~~~~~~~~~~~~~~~~~~~~ + +These profession definitions are designed to work well with or without the +`autohauler` plugin (which helps to keep your dwarves focused on skilled labors +instead of constantly being distracted by hauling). If you do want to use +autohauler, adding the following lines to your ``onMapLoad.init`` file will +configure it to let the professions manage the "Feed water to civilians" and +"Recover wounded" labors instead of enabling those labors for all hauling +dwarves:: + + on-new-fortress enable autohauler + on-new-fortress autohauler FEED_WATER_CIVILIANS allow + on-new-fortress autohauler RECOVER_WOUNDED allow .. _mousequery: diff --git a/docs/changelog.txt b/docs/changelog.txt index ccc94701c..29e0d8e22 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,8 +41,10 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled ## Misc Improvements -- ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. +- `manipulator`: add a library of useful default professions +- `manipulator`: move professions configuration from ``professions/`` to ``dfhack-config/professions/`` to keep it together with other dfhack configuration. If you have saved professions that you would like to keep, please manually move them to the new folder. +- ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. - `orders`: added useful library of manager orders. see them with ``orders list`` and import them with, for example, ``orders import library/basic`` - `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. diff --git a/docs/guides/examples-guide.rst b/docs/guides/examples-guide.rst index 58446506e..b699b8204 100644 --- a/docs/guides/examples-guide.rst +++ b/docs/guides/examples-guide.rst @@ -47,125 +47,3 @@ it is useful (and customizable) for any fort. It includes the following config: fortress is first started, so any later changes you make to autobutcher settings won't be overridden. - Enables `automelt`, `tailor`, `zone`, `nestboxes`, and `autonestbox`. - -The ``professions/`` subfolder ------------------------------- - -The :source:`professions/ ` subfolder contains -professions, or sets of related labors, that you can assign to your dwarves with -the DFHack `manipulator` plugin. Copy them into the ``professions/`` -subdirectory under the main Dwarf Fortress folder (you may have to create this -subdirectory) and assign them to your dwarves in the manipulator UI, accessible -from the ``units`` screen via the :kbd:`l` hotkey. Make sure that the -``manipulator`` plugin is enabled in your ``dfhack.init`` file! You can assign a -profession to a dwarf by selecting the dwarf in the ``manipulator`` UI and -hitting :kbd:`p`. The list of professions that you copied into the -``professions/`` folder will show up for you to choose from. This is very useful -for assigning roles to new migrants to ensure that all the tasks in your fort -have adequate numbers of dwarves attending to them. - -If you'd rather use Dwarf Therapist to manage your labors, it is easy to import -these professions to DT and use them there. Simply assign the professions you -want to import to a dwarf. Once you have assigned a profession to at least one -dwarf, you can select "Import Professions from DF" in the DT "File" menu. The -professions will then be available for use in DT. - -In the charts below, the "At Start" and "Max" columns indicate the approximate -number of dwarves of each profession that you are likely to need at the start of -the game and how many you are likely to need in a mature fort. - -============= ======== ===== ================================================= -Profession At Start Max Description -============= ======== ===== ================================================= -Chef 0 3 Buchery, Tanning, and Cooking. It is important to - focus just a few dwarves on cooking since - well-crafted meals make dwarves very happy. They - are also an excellent trade good. -Craftsdwarf 0 4-6 All labors used at Craftsdwarf's workshops, - Glassmaker's workshops, and kilns. -Doctor 0 2-4 The full suite of medical labors, plus Animal - Caretaking for those using the dwarfvet plugin. -Farmer 1 4 Food- and animal product-related labors. This - profession also has the ``Alchemist`` labor - enabled since they need to focus on food-related - jobs, though you might want to disable - ``Alchemist`` for your first farmer until there - are actual farming duties to perform. -Fisherdwarf 0 0-1 Fishing and fish cleaning. If you assign this - profession to any dwarf, be prepared to be - inundated with fish. Fisherdwarves *never stop - fishing*. Be sure to also run ``prioritize -a - PrepareRawFish ExtractFromRawFish`` (or use the - ``onMapLoad_dreamfort.init`` file above) or else - caught fish will just be left to rot. -Hauler 0 >20 All hauling labors plus Siege Operating, Mechanic - (so haulers can assist in reloading traps) and - Architecture (so haulers can help build massive - windmill farms and pump stacks). As you - accumulate enough Haulers, you can turn off - hauling labors for other dwarves so they can - focus on their skilled tasks. You may also want - to restrict your Mechanic's workshops to only - skilled mechanics so your haulers don't make - low-quality mechanisms. -Laborer 0 10-12 All labors that don't improve quality with skill, - such as Soapmaking and furnace labors. -Marksdwarf 0 10-30 Similar to Hauler. See the description for - Meleedwarf below for more details. -Mason 2 2-4 Masonry, Gem Cutting/Encrusting, and - Architecture. In the early game, you may need to - run "`prioritize` ConstructBuilding" to get your - masons to build wells and bridges if they are too - busy crafting stone furniture. -Meleedwarf 0 20-50 Similar to Hauler, but without most civilian - labors. This profession is separate from Hauler - so you can find your military dwarves easily. - Meleedwarves and Marksdwarves have Mechanics and - hauling labors enabled so you can temporarily - deactivate your military after sieges and allow - your military dwarves to help clean up. -Migrant 0 0 You can assign this profession to new migrants - temporarily while you sort them into professions. - Like Marksdwarf and Meleedwarf, the purpose of - this profession is so you can find your new - dwarves more easily. -Miner 2 2-10 Mining and Engraving. This profession also has - the ``Alchemist`` labor enabled, which disables - hauling for those using the `autohauler` plugin. - Once the need for Miners tapers off in the late - game, dwarves with this profession make good - military dwarves, wielding their picks as - weapons. -Outdoorsdwarf 1 2-4 Carpentry, Bowyery, Woodcutting, Animal Training, - Trapping, Plant Gathering, Beekeeping, and Siege - Engineering. -Smith 0 2-4 Smithing labors. You may want to specialize your - Smiths to focus on a single smithing skill to - maximize equipment quality. -StartManager 1 0 All skills not covered by the other starting - professions (Miner, Mason, Outdoorsdwarf, and - Farmer), plus a few overlapping skills to - assist in critical tasks at the beginning of the - game. Individual labors should be turned off as - migrants are assigned more specialized - professions that cover them, and the StartManager - dwarf can eventually convert to some other - profession. -Tailor 0 2 Textile industry labors: Dying, Leatherworking, - Weaving, and Clothesmaking. -============= ======== ===== ================================================= - -A note on autohauler -~~~~~~~~~~~~~~~~~~~~ - -These profession definitions are designed to work well with or without the -`autohauler` plugin (which helps to keep your dwarves focused on skilled labors -instead of constantly being distracted by hauling). If you do want to use -autohauler, adding the following lines to your ``onMapLoad.init`` file will -configure it to let the professions manage the "Feed water to civilians" and -"Recover wounded" labors instead of enabling those labors for all hauling -dwarves:: - - on-new-fortress enable autohauler - on-new-fortress autohauler FEED_WATER_CIVILIANS allow - on-new-fortress autohauler RECOVER_WOUNDED allow diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h index 08d48bd58..a9a7dc23c 100644 --- a/plugins/listcolumn.h +++ b/plugins/listcolumn.h @@ -1,3 +1,5 @@ +#pragma once + #include "uicommon.h" using df::global::enabler; diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 4604cd7ea..2d3f3bc0a 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -652,10 +652,12 @@ namespace unit_ops { struct ProfessionTemplate { std::string name; + std::string displayName; + bool library; bool mask; std::vector labors; - bool load(string directory, string file) + bool load(string directory, string file, bool isLibrary) { cerr << "Attempt to load " << file << endl; std::ifstream infile(directory + "/" + file); @@ -663,14 +665,25 @@ struct ProfessionTemplate return false; } + library = isLibrary; + std::string line; name = file; // If no name is given we default to the filename + displayName = name; mask = false; while (std::getline(infile, line)) { if (strcmp(line.substr(0,5).c_str(),"NAME ")==0) { auto nextInd = line.find(' '); - name = line.substr(nextInd + 1); + displayName = line.substr(nextInd + 1); + name = displayName; + size_t slashpos = name.find_first_of("\\/"); + while (name.npos != slashpos) { + name = name.substr(slashpos + 1); + slashpos = name.find_first_of("\\/"); + } + if (name == "") + name = file; continue; } if (line == "MASK") @@ -745,7 +758,8 @@ struct ProfessionTemplate } }; -static std::string professions_folder = Filesystem::getcwd() + "/professions"; +static std::string professions_folder = "dfhack-config/professions"; +static std::string professions_library_folder = "dfhack-config/professions/library"; class ProfessionTemplateManager { public: @@ -760,27 +774,21 @@ public: } void load() { - vector files; - cerr << "Attempting to load professions: " << professions_folder.c_str() << endl; if (!Filesystem::isdir(professions_folder) && !Filesystem::mkdir(professions_folder)) { cerr << professions_folder << ": Does not exist and cannot be created" << endl; return; } - Filesystem::listdir(professions_folder, files); - std::sort(files.begin(), files.end()); - for(size_t i = 0; i < files.size(); i++) - { - if (files[i] == "." || files[i] == "..") - continue; + _load(professions_folder, false); + _load(professions_library_folder, true); - ProfessionTemplate t; - if (t.load(professions_folder, files[i])) - { - templates.push_back(t); - } - } + // sort alphabetically by display name, with user data above library data + std::sort(templates.begin(), templates.end(), + [](const ProfessionTemplate &a, const ProfessionTemplate &b) { + return (a.library == b.library && a.displayName < b.displayName) + || (b.library && !a.library); + }); } void save_from_unit(UnitInfo *unit) { @@ -792,6 +800,21 @@ public: t.save(professions_folder); reload(); } + +private: + void _load(const std::string &path, bool library) { + vector files; + Filesystem::listdir(path, files); + for (auto &fname : files) { + if (Filesystem::isdir(path + "/" + fname)) + continue; + + ProfessionTemplate t; + if (t.load(path, fname, library)) { + templates.push_back(t); + } + } + } }; static ProfessionTemplateManager manager; @@ -992,7 +1015,7 @@ public: manager.reload(); for (size_t i = 0; i < manager.templates.size(); i++) { - std::string name = manager.templates[i].name; + std::string name = manager.templates[i].displayName; if (manager.templates[i].mask) name += " (mask)"; ListEntry elem(name, i); From 15aae9cf1ef90c862d75c28e4d81d93764c59a6e Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Thu, 7 Jul 2022 19:23:10 +0100 Subject: [PATCH 171/854] More guide --- docs/guides/modding-guide.rst | 79 ++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index b3b5c515b..085c53a1e 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -98,14 +98,83 @@ Then, we pass the key, amount of time units between function calls, what the tim Check the full list of events at https://docs.dfhack.org/en/stable/docs/Lua%20API.html#list-of-events. -Setting up an environment for a more advanced modular mod ---------------------------------------------------------- +Custom raw tokens +----------------- -Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. +In this section, we are going to use custom raw tokens applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how you could make boots that make units go faster when worn. Both of these involve custom raw tokens. + +First, let's define a custom crossbow with its own custom reaction. The crossbow: :: + + [ITEM_WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE] + [NAME:crossbow:crossbows] + [SIZE:600] + [SKILL:HAMMER] + [RANGED:CROSSBOW:BOLT] + [SHOOT_FORCE:4000] + [SHOOT_MAXVEL:800] + [TWO_HANDED:0] + [MINIMUM_SIZE:17500] + [MATERIAL_SIZE:4] + [ATTACK:BLUNT:10000:4000:bash:bashes:NO_SUB:1250] + [ATTACK_PREPARE_AND_RECOVER:3:3] + [FIRE_TIME:100] custom token (you'll see) + +The reaction to make it (you would add the reaction and not the weapon to an entity raw): :: + + [REACTION:MAKE_SIEGE_CROSSBOW] + [NAME:make siege crossbow] + [BUILDING:BOWYER:NONE] + [SKILL:BOWYER] + [REAGENT:mechanism 1:2:TRAPPARTS:NONE:NONE:NONE] + [REAGENT:bar:150:BAR:NONE:NONE:NONE] + [METAL_ITEM_MATERIAL] + [REAGENT:handle 1:1:BLOCKS:NONE:NONE:NONE] wooden handles + [ANY_PLANT_MATERIAL] + [REAGENT:handle 2:1:BLOCKS:NONE:NONE:NONE] + [ANY_PLANT_MATERIAL] + [TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] another custom token + [PRODUCT:100:1:WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE:GET_MATERIAL_FROM_REAGENT:bar:NONE] + +So, we are going to use the ``eventful`` module to make it so that (after the script is run) when this crossbow is crafted, it will have two handles, each with the material given by the block reagents. + +First, require the modules we are going to use. :: -TODO + local eventful = require("plugins.eventful") + local customRawTokens = require("custom-raw-tokens") + +Now, let's make a callback: :: + + local modId = "siege-crossbow" + eventful.onReactionComplete[modId] = function(reaction, reactionProduct, unit, inputItems, inputReagents, outputItems) + +First, we check to see if it the reaction that just happened is relevant to this callback: :: + + if not customRawTokens.getToken(reaction, "TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") then return end + +Then, we get the product number listed. Next, for every reagent, if the reagent name starts with "handle" then we get the corresponding item, and... :: + + for i, reagent in ipairs(inputReagents) do + if reagent.code:sub(1, #"handle") == "handle" then + -- Found handle reagent + local item = inputItems[i] -- hopefully found handle item + +...We then add a handle improvement to the listed product within our loop. :: + + local new = df.itemimprovement_itemspecificst:new() + new.mat_type, new.mat_index = item.mat_type, item.mat_index + -- new.maker = outputItems[0].maker -- not a typical improvement + new.type = df.itemimprovement_specific_type.HANDLE + outputItems[productNumber - 1].improvements:insert("#", new) + -- break -- multiple handles, multiple "the handle is made from"s, so no break + +It's all a bit loose and hacky but it works, at least if you don't have multiple stacks filling up one reagent. + +TODO: fire rate +TODO: "running shoes" Your first whole mod -------------------- -s +Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. + +TODO From 25175b5c2811d5f1a2da512485b8e1679ad2cde5 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Sat, 9 Jul 2022 17:04:20 +0100 Subject: [PATCH 172/854] Fire rate code --- docs/guides/modding-guide.rst | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 085c53a1e..15e96599d 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -117,7 +117,7 @@ First, let's define a custom crossbow with its own custom reaction. The crossbow [MATERIAL_SIZE:4] [ATTACK:BLUNT:10000:4000:bash:bashes:NO_SUB:1250] [ATTACK_PREPARE_AND_RECOVER:3:3] - [FIRE_TIME:100] custom token (you'll see) + [FIRE_RATE_MULTIPLIER:2] custom token (you'll see) The reaction to make it (you would add the reaction and not the weapon to an entity raw): :: @@ -169,7 +169,27 @@ Then, we get the product number listed. Next, for every reagent, if the reagent It's all a bit loose and hacky but it works, at least if you don't have multiple stacks filling up one reagent. -TODO: fire rate +Let's also make some code to modify the fire rate of the siege crossbow. :: + + eventful.onProjItemCheckMovement[modId] = function(projectile) + if projectile.distance_flown > 0 then -- don't repeat this + return + end + + local firer = projectile.firer + if not firer then + return + end + + local weapon = df.item.find(projectile.bow_id) + if not weapon then + return + end + + local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "FIRE_RATE_MULTIPLIER")) or 1 + firer.counters.think_counter = math.floor(firer.counters.think_counter * multiplier) + end + TODO: "running shoes" Your first whole mod From c7107e9c2356ab2eccec91d807c311396f012af7 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 9 Jul 2022 23:34:57 -0700 Subject: [PATCH 173/854] ignore docs in the scripts repo --- conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conf.py b/conf.py index 79b873bf9..ccad5c806 100644 --- a/conf.py +++ b/conf.py @@ -283,6 +283,7 @@ exclude_patterns = [ 'build*', 'docs/_auto/news*', 'docs/_changelogs/', + 'scripts/docs/', ] # The reST default role (used for this markup: `text`) to use for all From 9c32a52cb08af66603a593b70f9dd42d5e7c9ff4 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 9 Jul 2022 23:43:35 -0700 Subject: [PATCH 174/854] actually ignore the script docs --- conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf.py b/conf.py index ccad5c806..5ec58fc98 100644 --- a/conf.py +++ b/conf.py @@ -283,7 +283,7 @@ exclude_patterns = [ 'build*', 'docs/_auto/news*', 'docs/_changelogs/', - 'scripts/docs/', + 'scripts/docs/*', ] # The reST default role (used for this markup: `text`) to use for all From b560bcc25654cf3fcf2410b1c992ca0f141ad509 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sun, 10 Jul 2022 07:17:43 +0000 Subject: [PATCH 175/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 6bc683d2c..ef5fc459c 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 6bc683d2ca42ef467465b0905513ad590a7dc4c2 +Subproject commit ef5fc459c4f0a65a9fd709c14fed0c5892c9eebd From 28e15162a5921fab465a221ecfa998df33c7b3c7 Mon Sep 17 00:00:00 2001 From: Myk Date: Sun, 10 Jul 2022 08:54:55 -0700 Subject: [PATCH 176/854] reorganize init scripts into dfhack-config (#2232) * reorganize init scripts into dfhack-config allows player init scripts to build on defaults instead of replace them this also moves the init scripts out of the main df directory * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * escape asterisks in docs * remove unneeded dfhack.init file creation for test * write the test init script to the new init dir * create the init dir before trying to write a file * rename default init files for clarity * Update changelog * Update docs/changelog.txt Co-authored-by: Alan * Try to get buildmaster to work with old branches * Update changelog * get keybindings from all init scripts * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix spacing in changelog * split default loading into its own file * update docs with new changes * update help text wording in default init files * Apply suggestions from code review Co-authored-by: Alan * Alphabetize changelog * Update onMapLoad.default.init * Update onMapLoad.init * Update Core.rst Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Alan --- .github/workflows/build.yml | 1 - ci/run-tests.py | 11 +- conf.py | 36 +++-- data/CMakeLists.txt | 3 + data/init/dfhack.default.init | 8 + .../init/dfhack.keybindings.init | 140 +----------------- data/init/dfhack.tools.init | 131 ++++++++++++++++ data/init/onLoad.default.init | 5 + data/init/onMapLoad.default.init | 6 + data/init/onMapUnload.default.init | 5 + data/init/onUnload.default.init | 5 + dfhack-config/init/default.dfhack.init | 7 + dfhack-config/init/default.onLoad.init | 7 + dfhack-config/init/default.onMapLoad.init | 7 + dfhack-config/init/default.onMapUnload.init | 7 + dfhack-config/init/default.onUnload.init | 7 + dfhack-config/init/dfhack.init | 5 + dfhack-config/init/onLoad.init | 6 + dfhack-config/init/onMapLoad.init | 5 + dfhack-config/init/onMapUnload.init | 5 + dfhack-config/init/onUnload.init | 4 + docs/Core.rst | 104 +++++++------ docs/changelog.txt | 2 +- library/CMakeLists.txt | 3 - library/Core.cpp | 17 ++- 25 files changed, 329 insertions(+), 208 deletions(-) create mode 100644 data/init/dfhack.default.init rename dfhack.init-example => data/init/dfhack.keybindings.init (60%) create mode 100644 data/init/dfhack.tools.init create mode 100644 data/init/onLoad.default.init create mode 100644 data/init/onMapLoad.default.init create mode 100644 data/init/onMapUnload.default.init create mode 100644 data/init/onUnload.default.init create mode 100644 dfhack-config/init/default.dfhack.init create mode 100644 dfhack-config/init/default.onLoad.init create mode 100644 dfhack-config/init/default.onMapLoad.init create mode 100644 dfhack-config/init/default.onMapUnload.init create mode 100644 dfhack-config/init/default.onUnload.init create mode 100644 dfhack-config/init/dfhack.init create mode 100644 dfhack-config/init/onLoad.init create mode 100644 dfhack-config/init/onMapLoad.init create mode 100644 dfhack-config/init/onMapUnload.init create mode 100644 dfhack-config/init/onUnload.init diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2c710d91a..bef01888f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -100,7 +100,6 @@ jobs: run: | export TERM=dumb status=0 - mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init script -qe -c "python ci/run-tests.py --headless --keep-status \"$DF_FOLDER\"" || status=$((status + 1)) python ci/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt" || status=$((status + 2)) mkdir -p artifacts diff --git a/ci/run-tests.py b/ci/run-tests.py index be2a02b3f..a6a35bc76 100755 --- a/ci/run-tests.py +++ b/ci/run-tests.py @@ -68,7 +68,16 @@ init_contents = change_setting(init_contents, 'FPS', 'YES') if args.headless: init_contents = change_setting(init_contents, 'PRINT_MODE', 'TEXT') -test_init_file = 'dfhackzzz_test.init' # Core sorts these alphabetically +init_path = 'dfhack-config/init' +if not os.path.isdir('hack/init'): + # we're on an old branch that still reads init files from the root dir + init_path = '.' +try: + os.mkdir(init_path) +except OSError as error: + # ignore already exists errors + pass +test_init_file = os.path.join(init_path, 'dfhackzzz_test.init') # Core sorts these alphabetically with open(test_init_file, 'w') as f: f.write(''' devel/dump-rpc dfhack-rpc.txt diff --git a/conf.py b/conf.py index 5ec58fc98..71ba65fb7 100644 --- a/conf.py +++ b/conf.py @@ -24,32 +24,40 @@ import sys # -- Support :dfhack-keybind:`command` ------------------------------------ -# this is a custom directive that pulls info from dfhack.init-example +# this is a custom directive that pulls info from default keybindings from docutils import nodes from docutils.parsers.rst import roles sphinx_major_version = sphinx.version_info[0] -def get_keybinds(): +def get_keybinds(root, files, keybindings): + """Add keybindings in the specified files to the + given keybindings dict. + """ + for file in files: + with open(os.path.join(root, file)) as f: + lines = [l.replace('keybinding add', '').strip() for l in f.readlines() + if l.startswith('keybinding add')] + for k in lines: + first, command = k.split(' ', 1) + bind, context = (first.split('@') + [''])[:2] + if ' ' not in command: + command = command.replace('"', '') + tool = command.split(' ')[0].replace('"', '') + keybindings[tool] = keybindings.get(tool, []) + [ + (command, bind.split('-'), context)] + +def get_all_keybinds(root_dir): """Get the implemented keybinds, and return a dict of {tool: [(full_command, keybinding, context), ...]}. """ - with open('dfhack.init-example') as f: - lines = [l.replace('keybinding add', '').strip() for l in f.readlines() - if l.startswith('keybinding add')] keybindings = dict() - for k in lines: - first, command = k.split(' ', 1) - bind, context = (first.split('@') + [''])[:2] - if ' ' not in command: - command = command.replace('"', '') - tool = command.split(' ')[0].replace('"', '') - keybindings[tool] = keybindings.get(tool, []) + [ - (command, bind.split('-'), context)] + for root, _, files in os.walk(root_dir): + get_keybinds(root, files, keybindings) return keybindings -KEYBINDS = get_keybinds() +KEYBINDS = get_all_keybinds('data/init') # pylint:disable=unused-argument,dangerous-default-value,too-many-arguments diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 26befdb34..ea88d4473 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -1,3 +1,6 @@ +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/init/ + DESTINATION "${DFHACK_DATA_DESTINATION}/init") + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/quickfort/ DESTINATION "${DFHACK_DATA_DESTINATION}/data/quickfort") diff --git a/data/init/dfhack.default.init b/data/init/dfhack.default.init new file mode 100644 index 000000000..78dc70450 --- /dev/null +++ b/data/init/dfhack.default.init @@ -0,0 +1,8 @@ +# Default DFHack commands to run on program init + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/dfhack.init + +script hack/init/dfhack.keybindings.init +script hack/init/dfhack.tools.init diff --git a/dfhack.init-example b/data/init/dfhack.keybindings.init similarity index 60% rename from dfhack.init-example rename to data/init/dfhack.keybindings.init index 88528f650..e32a7c58e 100644 --- a/dfhack.init-example +++ b/data/init/dfhack.keybindings.init @@ -1,3 +1,9 @@ +# Default DFHack keybindings + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/dfhack.init + ############################## # Generic dwarfmode bindings # ############################## @@ -164,137 +170,3 @@ keybinding add Shift-B@pet/List/Unit "gui/autobutcher" # view pathable tiles from active cursor keybinding add Alt-Shift-P@dwarfmode/LookAround gui/pathable - -############################ -# UI and game logic tweaks # -############################ - -# stabilize the cursor of dwarfmode when switching menus -tweak stable-cursor - -# stop stacked liquid/bar/thread/cloth items from lasting forever -# if used in reactions that use only a fraction of the dimension. -# might be fixed by DF -# tweak fix-dimensions - -# make reactions requiring containers usable in advmode - the issue is -# that the screen asks for those reagents to be selected directly -tweak advmode-contained - -# support Shift-Enter in Trade and Move Goods to Depot screens for faster -# selection; it selects the current item or stack and scrolls down one line -tweak fast-trade - -# stop the right list in military->positions from resetting to top all the time -tweak military-stable-assign -# in same list, color units already assigned to squads in brown & green -tweak military-color-assigned - -# make crafted cloth items wear out with time like in old versions (bug 6003) -tweak craft-age-wear - -# stop adamantine clothing from wearing out (bug 6481) -#tweak adamantine-cloth-wear - -# Add "Select all" and "Deselect all" options to farm plot menus -tweak farm-plot-select - -# Add Shift-Left/Right controls to import agreement screen -tweak import-priority-category - -# Fixes a crash in the work order contition material list (bug 9905). -tweak condition-material - -# Adds an option to clear currently-bound hotkeys -tweak hotkey-clear - -# Allows lowercase letters in embark profile names, and allows exiting the name prompt without saving -tweak embark-profile-name - -# Reduce performance impact of temperature changes -tweak fast-heat 100 - -# Misc. UI tweaks -tweak block-labors # Prevents labors that can't be used from being toggled -tweak burrow-name-cancel -tweak cage-butcher -tweak civ-view-agreement -tweak do-job-now -tweak eggs-fertile -tweak fps-min -tweak hide-priority -tweak kitchen-prefs-all -tweak kitchen-prefs-empty -tweak max-wheelbarrow -tweak partial-items -tweak shift-8-scroll -tweak stone-status-all -tweak title-start-rename -tweak tradereq-pet-gender - -########################### -# Globally acting plugins # -########################### - -# Display DFHack version on title screen -enable title-version - -# Dwarf Manipulator (simple in-game Dwarf Therapist replacement) -enable manipulator - -# Search tool in various screens (by falconne) -enable search - -# Improved build material selection interface (by falconne) -enable automaterial - -# Other interface improvement tools -enable \ - confirm \ - dwarfmonitor \ - mousequery \ - autogems \ - autodump \ - automelt \ - autotrade \ - buildingplan \ - resume \ - trackstop \ - zone \ - stocks \ - autochop \ - stockpiles -#end a line with a backslash to make it continue to the next line. The \ is deleted for the final command. -# Multiline commands are ONLY supported for scripts like dfhack.init. You cannot do multiline command manually on the DFHack console. -# You cannot extend a commented line. -# You can comment out the extension of a line. - -# enable mouse controls and sand indicator in embark screen -embark-tools enable sticky sand mouse - -# enable option to enter embark assistant -enable embark-assistant - -########### -# Scripts # -########### - -# write extra information to the gamelog -modtools/extra-gamelog enable - -# extended status screen (bedrooms page) -enable gui/extended-status - -# add information to item viewscreens -view-item-info enable - -# a replacement for the "load game" screen -gui/load-screen enable - -############################## -# Extra DFHack command files # -############################## - -# Create a file named "onLoad.init" to run commands when a world is loaded -# and/or create a file named "onMapLoad.init" to run commands when a map is -# loaded. See the hack/examples/init/ directory for useful pre-made init files. diff --git a/data/init/dfhack.tools.init b/data/init/dfhack.tools.init new file mode 100644 index 000000000..aab17ebbe --- /dev/null +++ b/data/init/dfhack.tools.init @@ -0,0 +1,131 @@ +# Default DFHack tool configuration + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/dfhack.init + +############################ +# UI and game logic tweaks # +############################ + +# stabilize the cursor of dwarfmode when switching menus +tweak stable-cursor + +# stop stacked liquid/bar/thread/cloth items from lasting forever +# if used in reactions that use only a fraction of the dimension. +# might be fixed by DF +# tweak fix-dimensions + +# make reactions requiring containers usable in advmode - the issue is +# that the screen asks for those reagents to be selected directly +tweak advmode-contained + +# support Shift-Enter in Trade and Move Goods to Depot screens for faster +# selection; it selects the current item or stack and scrolls down one line +tweak fast-trade + +# stop the right list in military->positions from resetting to top all the time +tweak military-stable-assign +# in same list, color units already assigned to squads in brown & green +tweak military-color-assigned + +# make crafted cloth items wear out with time like in old versions (bug 6003) +tweak craft-age-wear + +# stop adamantine clothing from wearing out (bug 6481) +#tweak adamantine-cloth-wear + +# Add "Select all" and "Deselect all" options to farm plot menus +tweak farm-plot-select + +# Add Shift-Left/Right controls to import agreement screen +tweak import-priority-category + +# Fixes a crash in the work order contition material list (bug 9905). +tweak condition-material + +# Adds an option to clear currently-bound hotkeys +tweak hotkey-clear + +# Allows lowercase letters in embark profile names, and allows exiting the name prompt without saving +tweak embark-profile-name + +# Reduce performance impact of temperature changes +tweak fast-heat 100 + +# Misc. UI tweaks +tweak block-labors # Prevents labors that can't be used from being toggled +tweak burrow-name-cancel +tweak cage-butcher +tweak civ-view-agreement +tweak do-job-now +tweak eggs-fertile +tweak fps-min +tweak hide-priority +tweak kitchen-prefs-all +tweak kitchen-prefs-empty +tweak max-wheelbarrow +tweak partial-items +tweak shift-8-scroll +tweak stone-status-all +tweak title-start-rename +tweak tradereq-pet-gender + +########################### +# Globally acting plugins # +########################### + +# Display DFHack version on title screen +enable title-version + +# Dwarf Manipulator (simple in-game Dwarf Therapist replacement) +enable manipulator + +# Search tool in various screens (by falconne) +enable search + +# Improved build material selection interface (by falconne) +enable automaterial + +# Other interface improvement tools +enable \ + confirm \ + dwarfmonitor \ + mousequery \ + autogems \ + autodump \ + automelt \ + autotrade \ + buildingplan \ + resume \ + trackstop \ + zone \ + stocks \ + autochop \ + stockpiles +#end a line with a backslash to make it continue to the next line. The \ is deleted for the final command. +# Multiline commands are ONLY supported for scripts like dfhack.init. You cannot do multiline command manually on the DFHack console. +# You cannot extend a commented line. +# You can comment out the extension of a line. + +# enable mouse controls and sand indicator in embark screen +embark-tools enable sticky sand mouse + +# enable option to enter embark assistant +enable embark-assistant + +########### +# Scripts # +########### + +# write extra information to the gamelog +modtools/extra-gamelog enable + +# extended status screen (bedrooms page) +enable gui/extended-status + +# add information to item viewscreens +view-item-info enable + +# a replacement for the "load game" screen +gui/load-screen enable diff --git a/data/init/onLoad.default.init b/data/init/onLoad.default.init new file mode 100644 index 000000000..c13190357 --- /dev/null +++ b/data/init/onLoad.default.init @@ -0,0 +1,5 @@ +# Default DFHack commands to run when a world is loaded + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/onLoad.init diff --git a/data/init/onMapLoad.default.init b/data/init/onMapLoad.default.init new file mode 100644 index 000000000..44986a044 --- /dev/null +++ b/data/init/onMapLoad.default.init @@ -0,0 +1,6 @@ +# Default DFHack commands to run when a map is loaded, either in +# adventure or fort mode. + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/onMapLoad.init diff --git a/data/init/onMapUnload.default.init b/data/init/onMapUnload.default.init new file mode 100644 index 000000000..6441d72ff --- /dev/null +++ b/data/init/onMapUnload.default.init @@ -0,0 +1,5 @@ +# Default DFHack commands to run when a map is unloaded + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/onMapUnload.init diff --git a/data/init/onUnload.default.init b/data/init/onUnload.default.init new file mode 100644 index 000000000..9254a257b --- /dev/null +++ b/data/init/onUnload.default.init @@ -0,0 +1,5 @@ +# Default DFHack commands to run when a world is unloaded + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/onUnload.init diff --git a/dfhack-config/init/default.dfhack.init b/dfhack-config/init/default.dfhack.init new file mode 100644 index 000000000..aad18dd1c --- /dev/null +++ b/dfhack-config/init/default.dfhack.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/dfhack.default.init diff --git a/dfhack-config/init/default.onLoad.init b/dfhack-config/init/default.onLoad.init new file mode 100644 index 000000000..fe87d4209 --- /dev/null +++ b/dfhack-config/init/default.onLoad.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/onLoad.default.init diff --git a/dfhack-config/init/default.onMapLoad.init b/dfhack-config/init/default.onMapLoad.init new file mode 100644 index 000000000..9e781b924 --- /dev/null +++ b/dfhack-config/init/default.onMapLoad.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/onMapLoad.default.init diff --git a/dfhack-config/init/default.onMapUnload.init b/dfhack-config/init/default.onMapUnload.init new file mode 100644 index 000000000..716680fd0 --- /dev/null +++ b/dfhack-config/init/default.onMapUnload.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/onMapUnload.default.init diff --git a/dfhack-config/init/default.onUnload.init b/dfhack-config/init/default.onUnload.init new file mode 100644 index 000000000..712c35098 --- /dev/null +++ b/dfhack-config/init/default.onUnload.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/onUnload.default.init diff --git a/dfhack-config/init/dfhack.init b/dfhack-config/init/dfhack.init new file mode 100644 index 000000000..b05598f98 --- /dev/null +++ b/dfhack-config/init/dfhack.init @@ -0,0 +1,5 @@ +# This file runs when DFHack is initialized, when Dwarf Fortress is first +# started, before any world or save data is loaded. +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/dfhack-config/init/onLoad.init b/dfhack-config/init/onLoad.init new file mode 100644 index 000000000..ef4fd97af --- /dev/null +++ b/dfhack-config/init/onLoad.init @@ -0,0 +1,6 @@ +# This file runs when a world is loaded. This happens when you open a save file +# in fort, adventure, or legends mode. If a fort is being loaded, this file runs +# before any onMapLoad.init files. +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/dfhack-config/init/onMapLoad.init b/dfhack-config/init/onMapLoad.init new file mode 100644 index 000000000..90c6b9e14 --- /dev/null +++ b/dfhack-config/init/onMapLoad.init @@ -0,0 +1,5 @@ +# This file runs when a map is loaded in adventure or fort mode, after any +# onLoad.init files (which run earlier, when the world is loaded). +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/dfhack-config/init/onMapUnload.init b/dfhack-config/init/onMapUnload.init new file mode 100644 index 000000000..c513d9cae --- /dev/null +++ b/dfhack-config/init/onMapUnload.init @@ -0,0 +1,5 @@ +# This file runs when a fortress map is unloaded, before any onUnload.init files +# (which run later, when the world is unloaded). +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/dfhack-config/init/onUnload.init b/dfhack-config/init/onUnload.init new file mode 100644 index 000000000..c8ed3ab5b --- /dev/null +++ b/dfhack-config/init/onUnload.init @@ -0,0 +1,4 @@ +# This file runs when a world is unloaded. +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/docs/Core.rst b/docs/Core.rst index ef178cc6e..fe881a54d 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -366,16 +366,27 @@ The following commands are *not* built-in, but offer similarly useful functions. * `repeat` +.. _dfhack-config: + +Configuration Files +=================== + +Most DFHack settings can be changed by modifying files in the ``dfhack-config`` +folder (which is in the DF folder). The default versions of these files, if they +exist, are in ``dfhack-config/default`` and are installed when DFHack starts if +necessary. + .. _init-files: Init Files -========== +---------- .. contents:: :local: DFHack allows users to automatically run commonly-used DFHack commands -when DF is first loaded, when a game is loaded, and when a game is unloaded. +when DF is first loaded, when a world is loaded, when a map is loaded, when a +map is unloaded, and when a world is unloaded. Init scripts function the same way they would if the user manually typed in their contents, but are much more convenient. In order to facilitate @@ -385,32 +396,33 @@ save-specific init files in the save folders. DFHack looks for init files in three places each time they could be run: -#. The main DF directory +#. The :file:`dfhack-config/init` subdirectory in the main DF directory #. :file:`data/save/{world}/raw`, where ``world`` is the current save, and #. :file:`data/save/{world}/raw/objects` -When reading commands from dfhack.init or with the `script` command, if the final -character on a line is a backslash then the next uncommented line is considered a -continuation of that line, with the backslash deleted. Commented lines are skipped, -so it is possible to comment out parts of a command with the ``#`` character. +For each of those directories, all matching init files will be executed in +alphabetical order. +Before running matched init scripts in any of those locations, the +:file:`dfhack-config/init/default.*` file that matches the event will be run to +load DFHack defaults. Only the :file:`dfhack-config/init` directory is checked +for this file, not any :file:`raw` directories. If you want DFHack to load +without running any of its default configuration commands, edit the +:file:`dfhack-config/init/default.*` files and comment out the commands you see +there. -.. _dfhack.init: +When reading commands from the init files or with the `script` command, if the +final character on a line is a backslash then the next uncommented line is +considered a continuation of that line, with the backslash deleted. Commented +lines are skipped, so it is possible to comment out parts of a command with the +``#`` character. -dfhack*.init ------------- -If your DF folder contains at least one file named ``dfhack*.init`` -(where ``*`` is a placeholder for any string), then all such files -are executed in alphabetical order when DF is first started. - -DFHack is distributed with :download:`/dfhack.init-example` as an example -with an up-to-date collection of basic commands; mostly setting standard -keybindings and `enabling ` plugins. You are encouraged to look -through this file to learn which features it makes available under which -key combinations. You may also customise it and rename it to ``dfhack.init``. +.. _dfhack.init: -If your DF folder does not contain any ``dfhack*.init`` files, the example -will be run as a fallback. +dfhack\*.init +............. +On startup, DFHack looks for files of the form ``dfhack*.init`` (where ``*`` is +a placeholder for any string, including the empty string). These files are best used for keybindings and enabling persistent plugins which do not require a world to be loaded. @@ -418,51 +430,49 @@ which do not require a world to be loaded. .. _onLoad.init: -onLoad*.init ------------- +onLoad\*.init +............. When a world is loaded, DFHack looks for files of the form ``onLoad*.init``, where ``*`` can be any string, including the empty string. -All matching init files will be executed in alphabetical order. A world being loaded can mean a fortress, an adventurer, or legends mode. These files are best used for non-persistent commands, such as setting a `fix ` script to run on `repeat`. -.. _onUnload.init: +.. _onMapLoad.init: -onUnload*.init --------------- -When a world is unloaded, DFHack looks for files of the form ``onUnload*.init``. -Again, these files may be in any of the above three places. -All matching init files will be executed in alphebetical order. +onMapLoad\*.init +................ +When a map is loaded, either in adventure or fort mode, DFHack looks for files +of the form ``onMapLoad*.init``, where ``*`` can be any string, including the +empty string. -Modders often use such scripts to disable tools which should not affect -an unmodded save. +These files are best used for commands that are only relevant once there is a +game map loaded. -.. _other_init_files: -Other init files ----------------- +.. _onMapUnload.init: +.. _onUnload.init: -* ``onMapLoad*.init`` and ``onMapUnload*.init`` are run when a map, - distinct from a world, is loaded. This is good for map-affecting - commands (e.g. `clean`), or avoiding issues in Legends mode. +onMapUnload\*.init and onUnload\*.init +...................................... +When a map or world is unloaded, DFHack looks for files of the form +``onMapUnload*.init`` or ``onUnload*.init``, respectively. -* Any lua script named ``raw/init.d/*.lua``, in the save or main DF - directory, will be run when any world or that save is loaded. +Modders often use unload init scripts to disable tools which should not run +after a modded save is unloaded. -.. _dfhack-config: +.. _other_init_files: -Configuration Files -=================== +raw/init.d/\*.lua +................. + +Any lua script named ``raw/init.d/*.lua``, in the save or main DF directory, +will be run when any world or that save is loaded. -Some DFHack settings can be changed by modifying files in the ``dfhack-config`` -folder (which is in the DF folder). The default versions of these files, if they -exist, are in ``dfhack-config/default`` and are installed when DFHack starts if -necessary. .. _script-paths: diff --git a/docs/changelog.txt b/docs/changelog.txt index 29e0d8e22..31f8b4615 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,7 +41,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled ## Misc Improvements - +- Init scripts: ``dfhack.init`` and other init scripts have moved to ``dfhack-config/init/``. If you have customized your ``dfhack.init`` file and want to keep your changes, please move the part that you have customized to the new location at ``dfhack-config/init/dfhack.init``. If you do not have changes that you want to keep, do not copy anything, and the new defaults will be used automatically. - `manipulator`: add a library of useful default professions - `manipulator`: move professions configuration from ``professions/`` to ``dfhack-config/professions/`` to keep it together with other dfhack configuration. If you have saved professions that you would like to keep, please manually move them to the new folder. - ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 9e7bf8590..b3cdf93b9 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -460,9 +460,6 @@ endif() # install the offset file install(FILES xml/symbols.xml DESTINATION ${DFHACK_DATA_DESTINATION}) -# install the example autoexec file -install(FILES ../dfhack.init-example - DESTINATION ${DFHACK_BINARY_DESTINATION}) install(TARGETS dfhack-run dfhack-client binpatch LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} diff --git a/library/Core.cpp b/library/Core.cpp index 10f5057f3..aa43f3c2b 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1450,13 +1450,12 @@ static void run_dfhack_init(color_ostream &out, Core *core) return; } + // load baseline defaults + core->loadScriptFile(out, "dfhack-config/init/default.dfhack.init", false); + + // load user overrides std::vector prefixes(1, "dfhack"); - size_t count = loadScriptFiles(core, out, prefixes, "."); - if (!count || !Filesystem::isfile("dfhack.init")) - { - core->runCommand(out, "gui/no-dfhack-init"); - core->loadScriptFile(out, "dfhack.init-example", false); - } + loadScriptFiles(core, out, prefixes, "dfhack-config/init"); } // Load dfhack.init in a dedicated thread (non-interactive console mode) @@ -2226,7 +2225,11 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve auto i = table.find(event); if ( i != table.end() ) { const std::vector& set = i->second; - loadScriptFiles(this, out, set, "." ); + + // load baseline defaults + this->loadScriptFile(out, "dfhack-config/init/default." + set[0] + ".init", false); + + loadScriptFiles(this, out, set, "dfhack-config/init"); loadScriptFiles(this, out, set, rawFolder); loadScriptFiles(this, out, set, rawFolder + "objects/"); } From f1cb9b9a839b2f80f2b4b5daab3000503b1353fc Mon Sep 17 00:00:00 2001 From: Myk Date: Tue, 5 Jul 2022 12:21:41 -0700 Subject: [PATCH 177/854] Build and install text help alongside html (#2236) * build text docs alongside html also: - capture more doc dependencies that should cause rebuilds - move intermediate build output (doctree data) into build dir - allow sphinx build to multitask more for faster completion times * install text help alongside html help * update settings in docs build action --- .github/workflows/build.yml | 4 ++-- .gitignore | 1 + CMakeLists.txt | 42 ++++++++++++++++++++++++------------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bef01888f..fc6eac094 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -128,14 +128,14 @@ jobs: python-version: 3 - name: Install dependencies run: | - pip install 'sphinx<4.4.0' + pip install 'sphinx' - name: Clone DFHack uses: actions/checkout@v1 with: submodules: true - name: Build docs run: | - sphinx-build -W --keep-going -j3 --color . docs/html + sphinx-build -W --keep-going -j auto --color . docs/html - name: Upload docs uses: actions/upload-artifact@v1 with: diff --git a/.gitignore b/.gitignore index 8e401a7df..0b54f4a84 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ build/VC2010 docs/_* docs/html/ docs/pdf/ +docs/text/ # in-place build build/Makefile diff --git a/CMakeLists.txt b/CMakeLists.txt index 8271840e6..6bee1c04a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ # main project file. use it from a build sub-folder, see COMPILE for details ## some generic CMake magic -cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) +cmake_minimum_required(VERSION 3.6 FATAL_ERROR) cmake_policy(SET CMP0048 NEW) project(dfhack) @@ -450,37 +450,47 @@ if(BUILD_DOCS) message(SEND_ERROR "Sphinx not found but BUILD_DOCS enabled") endif() - file(GLOB SPHINX_DEPS - "${CMAKE_CURRENT_SOURCE_DIR}/docs/*.rst" - "${CMAKE_CURRENT_SOURCE_DIR}/docs/guides/*.rst" - "${CMAKE_CURRENT_SOURCE_DIR}/docs/changelog.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/docs/gen_changelog.py" + file(GLOB SPHINX_GLOB_DEPS + LIST_DIRECTORIES false "${CMAKE_CURRENT_SOURCE_DIR}/docs/images/*.png" "${CMAKE_CURRENT_SOURCE_DIR}/docs/styles/*" - "${CMAKE_CURRENT_SOURCE_DIR}/conf.py" - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/about.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*/about.txt" + "${CMAKE_CURRENT_SOURCE_DIR}/data/init/*init" + ) + file(GLOB_RECURSE SPHINX_GLOB_RECURSE_DEPS + "${CMAKE_CURRENT_SOURCE_DIR}/*.rst" + "${CMAKE_CURRENT_SOURCE_DIR}/changelog.txt" + ) + list(FILTER SPHINX_GLOB_RECURSE_DEPS + EXCLUDE REGEX "docs/_" ) file(GLOB_RECURSE SPHINX_SCRIPT_DEPS "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.lua" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.rb" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.txt" ) - set(SPHINX_DEPS ${SPHINX_DEPS} ${SPHINX_SCRIPT_DEPS} - "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.rst" + set(SPHINX_DEPS ${SPHINX_GLOB_DEPS} ${SPHINX_GLOB_RECURSE_DEPS} ${SPHINX_SCRIPT_DEPS} "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt" + "${CMAKE_CURRENT_SOURCE_DIR}/conf.py" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/gen_changelog.py" ) set(SPHINX_OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/docs/html/.buildinfo") set_source_files_properties(${SPHINX_OUTPUT} PROPERTIES GENERATED TRUE) add_custom_command(OUTPUT ${SPHINX_OUTPUT} COMMAND ${SPHINX_EXECUTABLE} - -a -E -q -b html + -q -b html -d "${CMAKE_BINARY_DIR}/docs/html" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/docs/html" - -w "${CMAKE_CURRENT_SOURCE_DIR}/docs/_sphinx-warnings.txt" - -j 2 + -w "${CMAKE_BINARY_DIR}/docs/html/_sphinx-warnings.txt" + -j auto + COMMAND ${SPHINX_EXECUTABLE} + -q -b text -d "${CMAKE_BINARY_DIR}/docs/text" + "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/text" + -w "${CMAKE_BINARY_DIR}/docs/text/_sphinx-warnings.txt" + -j auto DEPENDS ${SPHINX_DEPS} - COMMENT "Building HTML documentation with Sphinx" + COMMENT "Building documentation with Sphinx" ) add_custom_target(dfhack_docs ALL @@ -493,6 +503,8 @@ if(BUILD_DOCS) install(DIRECTORY ${dfhack_SOURCE_DIR}/docs/html/ DESTINATION ${DFHACK_USERDOC_DESTINATION}/docs) + install(DIRECTORY ${dfhack_SOURCE_DIR}/docs/text/ + DESTINATION ${DFHACK_USERDOC_DESTINATION}/docs) install(FILES docs/_auto/news.rst docs/_auto/news-dev.rst DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(FILES "README.html" DESTINATION "${DFHACK_DATA_DESTINATION}") endif() From 27d7c3acc64009f0f4e361a4c3da891e9df1b239 Mon Sep 17 00:00:00 2001 From: Myk Date: Tue, 5 Jul 2022 14:05:34 -0700 Subject: [PATCH 178/854] Myk sample command help (#2238) * add example tool documentation in proposed format * refine Tools.rst --- conf.py | 6 +----- docs/Tools.rst | 13 +++++++++++++ docs/tools/cromulate.rst | 34 ++++++++++++++++++++++++++++++++++ index.rst | 1 + 4 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 docs/Tools.rst create mode 100644 docs/tools/cromulate.rst diff --git a/conf.py b/conf.py index 71ba65fb7..53b192467 100644 --- a/conf.py +++ b/conf.py @@ -66,11 +66,7 @@ def dfhack_keybind_role_func(role, rawtext, text, lineno, inliner, """Custom role parser for DFHack default keybinds.""" roles.set_classes(options) if text not in KEYBINDS: - msg = inliner.reporter.error( - 'no keybinding for {} in dfhack.init-example'.format(text), - line=lineno) - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] + return [], [] newnode = nodes.paragraph() for cmd, key, ctx in KEYBINDS[text]: n = nodes.paragraph() diff --git a/docs/Tools.rst b/docs/Tools.rst new file mode 100644 index 000000000..15c5f878e --- /dev/null +++ b/docs/Tools.rst @@ -0,0 +1,13 @@ +.. _tools-index: + +############ +DFHack Tools +############ + +These are the DFHack commands you can run. + +.. toctree:: + :titlesonly: + :glob: + + /docs/tools/* diff --git a/docs/tools/cromulate.rst b/docs/tools/cromulate.rst new file mode 100644 index 000000000..ca07ae0ca --- /dev/null +++ b/docs/tools/cromulate.rst @@ -0,0 +1,34 @@ +cromulate +========= + +Tags: productivity, unit, adventure +:dfhack-keybind:`cromulate` + +Collects all widgets into a frobozz electric cromufiler. You might want to do +this if you discover that your widgets have become decromulated. It is safe to +run this command periodically even if you are unsure if that's the case. + +Usage:: + + cromulate [all|here] [] + +When run without parameters, it lists all your widgets. Add the ``all`` keyword +to collect all widgets into the cromufiler, or the ``here`` keyword to just +collect those under the cursor. + +Options: + +:``-d``, ``--destroy``: + Destroy the widgets instead of collecting them into the cromufiler. +:``-q``, ``--quiet``: + Don't display any informational output. Errors will still be printed to the + console. + +Examples: + +- ``cromulate`` + Lists all widgets and their positions +- ``cromlate all`` + Gather all widgets into the cromufiler +- ``cromulate here --destroy`` + Destroys the widgets under the cursor diff --git a/index.rst b/index.rst index 2e5454f40..63ab02a86 100644 --- a/index.rst +++ b/index.rst @@ -32,6 +32,7 @@ User Manual /docs/Core /docs/Plugins /docs/Scripts + /docs/Tools /docs/guides/index /docs/index-about /docs/index-dev From b0e7325d4fc54ab9d0cf6b2876351bcc7df0823a Mon Sep 17 00:00:00 2001 From: Myk Date: Tue, 5 Jul 2022 14:13:25 -0700 Subject: [PATCH 179/854] add sample plugin to go with the sample help (#2239) --- plugins/CMakeLists.txt | 1 + plugins/cromulate.cpp | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 plugins/cromulate.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4a4b74eaa..732673926 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -106,6 +106,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(command-prompt command-prompt.cpp) dfhack_plugin(confirm confirm.cpp LINK_LIBRARIES lua) dfhack_plugin(createitem createitem.cpp) + dfhack_plugin(cromulate cromulate.cpp) dfhack_plugin(cursecheck cursecheck.cpp) dfhack_plugin(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) dfhack_plugin(deramp deramp.cpp) diff --git a/plugins/cromulate.cpp b/plugins/cromulate.cpp new file mode 100644 index 000000000..9f7fdb9f9 --- /dev/null +++ b/plugins/cromulate.cpp @@ -0,0 +1,28 @@ +#include "Core.h" +#include +#include +#include + +using namespace DFHack; +using namespace df::enums; + +DFHACK_PLUGIN("cromulate"); + +command_result cromulate (color_ostream &out, std::vector & parameters); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand("cromulate", + "in-cpp plugin short desc", //to use one line in the ``[DFHack]# ls`` output + cromulate, + false, + "in-cpp plugin long help")); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + return CR_OK; +} + +command_result cromulate (color_ostream &out, std::vector ¶meters) { + return CR_OK; +} From 64aba7a8a38f87dfb8dc984ccefb773e871c4cbe Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 6 Jul 2022 11:57:48 -0700 Subject: [PATCH 180/854] Myk rendered help (#2240) * implement help db * add initial list of tags * read tags fr help, read script short desc, filter --- docs/Tags.rst | 14 ++ index.rst | 1 + library/LuaApi.cpp | 76 ++++++++ library/lua/helpdb.lua | 410 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 501 insertions(+) create mode 100644 docs/Tags.rst create mode 100644 library/lua/helpdb.lua diff --git a/docs/Tags.rst b/docs/Tags.rst new file mode 100644 index 000000000..effc0ad23 --- /dev/null +++ b/docs/Tags.rst @@ -0,0 +1,14 @@ +Tags +==== + +This is the list of tags and their descriptions. + +- adventure: tools relevant to adventure mode +- fort: tools relevant to fort mode +- legends: tools relevant to legends mode +- enable: tools that are able to be enabled/disabled for some persistent effect +- items: tools that create or modify in-game items +- units: tools that create or modify units +- jobs: tools that create or modify jobs +- labors: tools that deal with labor assignment +- auto: tools that automatically manage some aspect of your fortress diff --git a/index.rst b/index.rst index 63ab02a86..3e761bbc6 100644 --- a/index.rst +++ b/index.rst @@ -32,6 +32,7 @@ User Manual /docs/Core /docs/Plugins /docs/Scripts + /docs/Tags /docs/Tools /docs/guides/index /docs/index-about diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 101b645fd..fb557329d 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -3023,6 +3023,79 @@ static int internal_findScript(lua_State *L) return 1; } +static int internal_listPlugins(lua_State *L) +{ + auto plugins = Core::getInstance().getPluginManager(); + + int i = 1; + lua_newtable(L); + for (auto it = plugins->begin(); it != plugins->end(); ++it) + { + lua_pushinteger(L, i++); + lua_pushstring(L, it->first.c_str()); + lua_settable(L, -3); + } + return 1; +} + +static int internal_listCommands(lua_State *L) +{ + auto plugins = Core::getInstance().getPluginManager(); + + const char *name = luaL_checkstring(L, 1); + + auto plugin = plugins->getPluginByName(name); + if (!plugin) + { + lua_pushnil(L); + return 1; + } + + size_t num_commands = plugin->size(); + lua_newtable(L); + for (size_t i = 0; i < num_commands; ++i) + { + lua_pushinteger(L, i + 1); + lua_pushstring(L, (*plugin)[i].name.c_str()); + lua_settable(L, -3); + } + return 1; +} +static int internal_getCommandHelp(lua_State *L) +{ + auto plugins = Core::getInstance().getPluginManager(); + + const char *name = luaL_checkstring(L, 1); + + auto plugin = plugins->getPluginByCommand(name); + if (!plugin) + { + lua_pushnil(L); + return 1; + } + + size_t num_commands = plugin->size(); + for (size_t i = 0; i < num_commands; ++i) + { + if ((*plugin)[i].name == name) + { + const auto &pc = (*plugin)[i]; + std::string help = pc.description; + if (help.size() && help[help.size()-1] != '.') + { + help += "."; + } + help += "\n" + pc.usage; + lua_pushstring(L, help.c_str()); + return 1; + } + } + + // not found (somehow) + lua_pushnil(L); + return 1; +} + static int internal_threadid(lua_State *L) { std::stringstream ss; @@ -3094,6 +3167,9 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "removeScriptPath", internal_removeScriptPath }, { "getScriptPaths", internal_getScriptPaths }, { "findScript", internal_findScript }, + { "listPlugins", internal_listPlugins }, + { "listCommands", internal_listCommands }, + { "getCommandHelp", internal_getCommandHelp }, { "threadid", internal_threadid }, { "md5File", internal_md5file }, { NULL, NULL } diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua new file mode 100644 index 000000000..cc8665aa1 --- /dev/null +++ b/library/lua/helpdb.lua @@ -0,0 +1,410 @@ +-- The help text database. +-- +-- Command help is read from the the following sources: +-- 1. rendered text in hack/docs/docs/ +-- 2. (for scripts) the script sources if no pre-rendered text exists or if the +-- script file has a modification time that is more recent than the +-- pre-rendered text +-- 3. (for plugins) the string passed to the PluginCommand initializer if no +-- pre-rendered text exists +-- +-- For plugins that don't register any commands, the plugin name serves as the +-- command so documentation on what happens when you enable the plugin can be +-- found. + +local _ENV = mkmodule('helpdb') + +local RENDERED_PATH = 'hack/docs/docs/tools/' +local TAG_DEFINITIONS = 'hack/docs/docs/Tags.txt' + +local SCRIPT_DOC_BEGIN = '[====[' +local SCRIPT_DOC_END = ']====]' + +local SOURCES = { + STUB='stub', + RENDERED='rendered', + PLUGIN='plugin', + SCRIPT='script', +} + +-- command name -> {short_help, long_help, tags, source, source_timestamp} +-- also includes a script_source_path element if the source is a script +-- and a unrunnable boolean if the source is a plugin that does not provide any +-- commands to invoke directly. +db = db or {} + +-- tag name -> list of command names +tag_index = tag_index or {} + +local function get_rendered_path(command) + return RENDERED_PATH .. command .. '.txt' +end + +local function has_rendered_help(command) + return dfhack.filesystem.mtime(get_rendered_path(command)) ~= -1 +end + +local DEFAULT_HELP_TEMPLATE = [[ +%s +%s + +Tags: None + +No help available. +]] + +local function make_default_entry(command, source) + local default_long_help = DEFAULT_HELP_TEMPLATE:format( + command, ('*'):rep(#command)) + return {short_help='No help available.', long_help=default_long_help, + tags={}, source=source, source_timestamp=0} +end + +-- updates the short_text, the long_text, and the tags in the given entry +local function update_entry(entry, iterator, opts) + opts = opts or {} + local lines = {} + local begin_marker_found,header_found = not opts.begin_marker,opts.no_header + local tags_found, short_help_found, in_short_help = false, false, false + for line in iterator do + if not short_help_found and opts.first_line_is_short_help then + local _,_,text = line:trim():find('^%-%-%s*(.*)') + if text[#text] ~= '.' then + text = text .. '.' + end + entry.short_help = text + short_help_found = true + goto continue + end + if not begin_marker_found then + local _, endpos = line:find(opts.begin_marker, 1, true) + if endpos == #line then + begin_marker_found = true + end + goto continue + end + if opts.end_marker then + local _, endpos = line:find(opts.end_marker, 1, true) + if endpos == #line then + break + end + end + if not header_found and line:find('%w') then + header_found = true + elseif not tags_found and line:find('^Tags: [%w, ]+$') then + -- tags must appear before the help text begins + local _,_,tags = line:trim():find('Tags: (.*)') + entry.tags = tags:split('[ ,]+') + table.sort(entry.tags) + tags_found = true + elseif not short_help_found and not line:find('^Keybinding:') and + line:find('%w') then + if in_short_help then + entry.short_help = entry.short_help .. ' ' .. line + else + entry.short_help = line + end + local sentence_end = entry.short_help:find('.', 1, true) + if sentence_end then + entry.short_help = entry.short_help:sub(1, sentence_end) + short_help_found = true + else + in_short_help = true + end + end + table.insert(lines, line) + ::continue:: + end + entry.long_help = table.concat(lines, '\n') +end + +local function make_rendered_entry(old_entry, command) + local rendered_path = get_rendered_path(command) + local source_timestamp = dfhack.filesystem.mtime(rendered_path) + if old_entry and old_entry.source == SOURCES.RENDERED and + old_entry.source_timestamp >= source_timestamp then + -- we already have the latest info + return old_entry + end + local entry = make_default_entry(command, SOURCES.RENDERED) + update_entry(entry, io.lines(rendered_path)) + entry.source_timestamp = source_timestamp + return entry +end + +local function make_plugin_entry(old_entry, command) + if old_entry and old_entry.source == SOURCES.PLUGIN then + -- we can't tell when a plugin is reloaded, so we can either choose to + -- always refresh or never refresh. let's go with never for now for + -- performance. + return old_entry + end + local entry = make_default_entry(command, SOURCES.PLUGIN) + local long_help = dfhack.internal.getCommandHelp(command) + if long_help and #long_help:trim() > 0 then + update_entry(entry, long_help:trim():gmatch('[^\n]*'), {no_header=true}) + end + return entry +end + +local function make_script_entry(old_entry, command, script_source_path) + local source_timestamp = dfhack.filesystem.mtime(script_source_path) + if old_entry and old_entry.source == SOURCES.SCRIPT and + old_entry.script_source_path == script_source_path and + old_entry.source_timestamp >= source_timestamp then + -- we already have the latest info + return old_entry + end + local entry = make_default_entry(command, SOURCES.SCRIPT) + update_entry(entry, io.lines(script_source_path), + {begin_marker=SCRIPT_DOC_BEGIN, end_marker=SCRIPT_DOC_END, + first_line_is_short_help=true}) + entry.source_timestamp = source_timestamp + return entry +end + +local function update_db(old_db, db, source, command, flags) + if db[command] then + -- already in db (e.g. from a higher-priority script dir); skip + return + end + local entry, old_entry = nil, old_db[command] + if source == SOURCES.RENDERED then + entry = make_rendered_entry(old_entry, command) + elseif source == SOURCES.PLUGIN then + entry = make_plugin_entry(old_entry, command) + elseif source == SOURCES.SCRIPT then + entry = make_script_entry(old_entry, command, flags.script_source) + elseif source == SOURCES.STUB then + entry = make_default_entry(command, SOURCES.STUB) + else + error('unhandled help source: ' .. source) + end + + entry.unrunnable = (flags or {}).unrunnable + + db[command] = entry + for _,tag in ipairs(entry.tags) do + -- unknown tags are ignored + if tag_index[tag] then + table.insert(tag_index[tag], command) + end + end +end + +local function scan_plugins(old_db, db) + local plugin_names = dfhack.internal.listPlugins() + for _,plugin in ipairs(plugin_names) do + local commands = dfhack.internal.listCommands(plugin) + if #commands == 0 then + -- use plugin name as the command so we have something to anchor the + -- documentation to + update_db(old_db, db, + has_rendered_help(plugin) and + SOURCES.RENDERED or SOURCES.STUB, + plugin, {unrunnable=true}) + goto continue + end + for _,command in ipairs(commands) do + update_db(old_db, db, + has_rendered_help(command) and + SOURCES.RENDERED or SOURCES.PLUGIN, + command) + end + ::continue:: + end +end + +local function scan_scripts(old_db, db) + for _,script_path in ipairs(dfhack.internal.getScriptPaths()) do + local files = dfhack.filesystem.listdir_recursive( + script_path, nil, false) + if not files then goto skip_path end + for _,f in ipairs(files) do + if f.isdir or not f.path:endswith('.lua') or + f.path:startswith('test/') or + f.path:startswith('internal/') then + goto continue + end + local script_source = script_path .. '/' .. f.path + local script_is_newer = dfhack.filesystem.mtime(script_source) > + dfhack.filesystem.mtime(get_rendered_path(f.path)) + update_db(old_db, db, + script_is_newer and SOURCES.SCRIPT or SOURCES.RENDERED, + f.path:sub(1, #f.path - 4), {script_source=script_source}) + ::continue:: + end + ::skip_path:: + end +end + +local function initialize_tags() + local tag, desc, in_desc = nil, nil, false + for line in io.lines(TAG_DEFINITIONS) do + if in_desc then + line = line:trim() + if #line == 0 then + in_desc = false + goto continue + end + desc = desc .. ' ' .. line + tag_index[tag].description = desc + else + _,_,tag,desc = line:find('^%* (%w+): (.+)') + if not tag then goto continue end + tag_index[tag] = {description=desc} + in_desc = true + end + ::continue:: + end +end + +-- ensures the db is up to date by scanning all help sources. does not do +-- anything if it has already been run within the last second. +last_refresh_ms = last_refresh_ms or 0 +local function ensure_db() + local now_ms = dfhack.getTickCount() + if now_ms - last_refresh_ms < 1000 then return end + last_refresh_ms = now_ms + + local old_db = db + db, tag_index = {}, {} + + initialize_tags() + scan_plugins(old_db, db) + scan_scripts(old_db, db) +end + +local function get_db_property(command, property) + ensure_db() + if not db[command] then + error(('command not found: "%s"'):format(command)) + end + return db[command][property] +end + +-- returns the ~54 char summary blurb associated with the entry +function get_entry_short_help(entry) + return get_db_property(entry, 'short_help') +end + +-- returns the full help documentation associated with the entry +function get_entry_long_help(entry) + return get_db_property(entry, 'long_help') +end + +-- returns the list of tags associated with the entry +function get_entry_tags(entry) + return get_db_property(entry, 'tags') +end + +local function chunk_for_sorting(str) + local parts = str:split('/') + local chunks = {} + for i=1,#parts do + chunks[#parts - i + 1] = parts[i] + end + return chunks +end + +-- sorts by last path component, then by parent path components. +-- something comes before nothing. +-- e.g. gui/autofarm comes immediately before autofarm +local function sort_by_basename(a, b) + local a = chunk_for_sorting(a) + local b = chunk_for_sorting(b) + local i = 1 + while a[i] do + if not b[i] then + return true + end + if a[i] ~= b[i] then + return a[i] < b[i] + end + i = i + 1 + end + return false +end + +local function add_if_matched(commands, command, strs, runnable) + if runnable and db[command].unrunnable then + return + end + if strs then + local matched = false + for _,str in ipairs(strs) do + if command:find(str, 1, true) then + matched = true + break + end + end + if not matched then + return + end + end + table.insert(commands, command) +end + +-- returns a list of identifiers, alphabetized by their last path component +-- (e.g. gui/autobutcher will immediately follow autobutcher). +-- the optional filter element is a map with the following elements: +-- str - if a string, filters by the given substring. if a table of strings, +-- includes commands that match any of the given substrings. +-- tag - if a string, filters by the given tag name. if a table of strings, +-- includes commands that match any of the given tags. +-- runnable - if true, filters out plugin names that do no correspond to +-- runnable commands. +function list_entries(filter) + ensure_db() + filter = filter or {} + local commands = {} + local strs = filter.str + if filter.str then + if type(strs) == 'string' then strs = {strs} end + end + local runnable = filter.runnable + if not filter.tag then + for command in pairs(db) do + add_if_matched(commands, command, strs, runnable) + end + else + local command_set = {} + local tags = filter.tag + if type(tags) == 'string' then tags = {tags} end + for _,tag in ipairs(tags) do + if not tag_index[tag] then + error('invalid tag: ' .. tag) + end + for _,command in ipairs(tag_index[tag]) do + command_set[command] = true + end + end + for command in pairs(command_set) do + add_if_matched(commands, command, strs, runnable) + end + end + table.sort(commands, sort_by_basename) + return commands +end + +-- returns the defined tags in alphabetical order +function list_tags() + ensure_db() + local tags = {} + for tag in pairs(tag_index) do + table.insert(tags, tag) + end + table.sort(tags) + return tags +end + +-- returns the description associated with the given tag +function get_tag_description(tag) + ensure_db() + if not tag_index[tag] then + error('invalid tag: ' .. tag) + end + return tag_index[tag].description +end + +return _ENV From 35a4d19ac9efce6081091fa5a63a21a3a192a146 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 6 Jul 2022 22:26:06 -0700 Subject: [PATCH 181/854] implement listing functions for helpdb --- library/lua/helpdb.lua | 152 +++++++++++++++++++++++++++-------------- 1 file changed, 102 insertions(+), 50 deletions(-) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index cc8665aa1..8a27abad8 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -69,7 +69,7 @@ local function update_entry(entry, iterator, opts) for line in iterator do if not short_help_found and opts.first_line_is_short_help then local _,_,text = line:trim():find('^%-%-%s*(.*)') - if text[#text] ~= '.' then + if not text:endswith('.') then text = text .. '.' end entry.short_help = text @@ -92,10 +92,11 @@ local function update_entry(entry, iterator, opts) if not header_found and line:find('%w') then header_found = true elseif not tags_found and line:find('^Tags: [%w, ]+$') then - -- tags must appear before the help text begins local _,_,tags = line:trim():find('Tags: (.*)') - entry.tags = tags:split('[ ,]+') - table.sort(entry.tags) + entry.tags = {} + for _,tag in ipairs(tags:split('[ ,]+')) do + entry.tags[tag] = true + end tags_found = true elseif not short_help_found and not line:find('^Keybinding:') and line:find('%w') then @@ -260,11 +261,11 @@ local function initialize_tags() end -- ensures the db is up to date by scanning all help sources. does not do --- anything if it has already been run within the last second. +-- anything if it has already been run within the last 10 seconds. last_refresh_ms = last_refresh_ms or 0 local function ensure_db() local now_ms = dfhack.getTickCount() - if now_ms - last_refresh_ms < 1000 then return end + if now_ms - last_refresh_ms < 60000 then return end last_refresh_ms = now_ms local old_db = db @@ -293,9 +294,18 @@ function get_entry_long_help(entry) return get_db_property(entry, 'long_help') end --- returns the list of tags associated with the entry +local function set_to_sorted_list(set) + local list = {} + for item in pairs(set) do + table.insert(list, item) + end + table.sort(list) + return list +end + +-- returns the list of tags associated with the entry, in alphabetical order function get_entry_tags(entry) - return get_db_property(entry, 'tags') + return set_to_sorted_list(get_db_property(entry, 'tags')) end local function chunk_for_sorting(str) @@ -326,76 +336,109 @@ local function sort_by_basename(a, b) return false end -local function add_if_matched(commands, command, strs, runnable) - if runnable and db[command].unrunnable then - return +local function matches(command, filter) + local db_entry = db[command] + if filter.runnable and db_entry.unrunnable then + return false + end + if filter.tag then + local matched = false + for _,tag in ipairs(filter.tag) do + if db_entry.tags[tag] then + matched = true + break + end + end + if not matched then + return false + end end - if strs then + if filter.str then local matched = false - for _,str in ipairs(strs) do + for _,str in ipairs(filter.str) do if command:find(str, 1, true) then matched = true break end end if not matched then - return + return false end end - table.insert(commands, command) + return true +end + +local function normalize_string_list(l) + if not l then return nil end + if type(l) == 'string' then + return {l} + end + return l +end + +local function normalize_filter(f) + if not f then return nil end + local filter = {} + filter.str = normalize_string_list(f.str) + filter.tag = normalize_string_list(f.tag) + filter.runnable = f.runnable + if not filter.str and not filter.tag and not filter.runnable then + return nil + end + return filter end -- returns a list of identifiers, alphabetized by their last path component -- (e.g. gui/autobutcher will immediately follow autobutcher). --- the optional filter element is a map with the following elements: +-- the optional include and exclude filter params are maps with the following +-- elements: -- str - if a string, filters by the given substring. if a table of strings, -- includes commands that match any of the given substrings. -- tag - if a string, filters by the given tag name. if a table of strings, -- includes commands that match any of the given tags. --- runnable - if true, filters out plugin names that do no correspond to --- runnable commands. -function list_entries(filter) +-- runnable - if true, matches only runnable commands, not plugin names. +function get_entries(include, exclude) ensure_db() - filter = filter or {} + include = normalize_filter(include) + exclude = normalize_filter(exclude) local commands = {} - local strs = filter.str - if filter.str then - if type(strs) == 'string' then strs = {strs} end - end - local runnable = filter.runnable - if not filter.tag then - for command in pairs(db) do - add_if_matched(commands, command, strs, runnable) - end - else - local command_set = {} - local tags = filter.tag - if type(tags) == 'string' then tags = {tags} end - for _,tag in ipairs(tags) do - if not tag_index[tag] then - error('invalid tag: ' .. tag) - end - for _,command in ipairs(tag_index[tag]) do - command_set[command] = true - end - end - for command in pairs(command_set) do - add_if_matched(commands, command, strs, runnable) + for command in pairs(db) do + if (not include or matches(command, include)) and + (not exclude or not matches(command, exclude)) then + table.insert(commands, command) end end table.sort(commands, sort_by_basename) return commands end +local function get_max_width(list, min_width) + local width = min_width or 0 + for _,item in ipairs(list) do + width = math.max(width, #item) + end + return width +end + +-- prints the requested entries to the console. include and exclude filters are +-- as in get_entries above. +function list_entries(include_tags, include, exclude) + local entries = get_entries(include, exclude) + local width = get_max_width(entries, 10) + for _,entry in ipairs(entries) do + print((' %-'..width..'s %s'):format( + entry, get_entry_short_help(entry))) + if include_tags then + print((' '..(' '):rep(width)..' tags(%s)'):format( + table.concat(get_entry_tags(entry), ','))) + end + end +end + -- returns the defined tags in alphabetical order -function list_tags() +function get_tags() ensure_db() - local tags = {} - for tag in pairs(tag_index) do - table.insert(tags, tag) - end - table.sort(tags) - return tags + return set_to_sorted_list(tag_index) end -- returns the description associated with the given tag @@ -407,4 +450,13 @@ function get_tag_description(tag) return tag_index[tag].description end +-- prints the defined tags and their descriptions to the console +function list_tags() + local tags = get_tags() + local width = get_max_width(tags, 10) + for _,tag in ipairs(tags) do + print((' %-'..width..'s %s'):format(tag, get_tag_description(tag))) + end +end + return _ENV From 4ad8e7199a691f900cac1824d0f746924eb748c4 Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 8 Jul 2022 14:43:41 -0700 Subject: [PATCH 182/854] Support builtin commands in helpdb (#2241) * support builtin commands in helpdb, implement list API, document api --- docs/Builtin.rst | 269 +++++++++++++++++++++++++++ docs/Core.rst | 264 +-------------------------- index.rst | 1 + library/lua/helpdb.lua | 405 ++++++++++++++++++++++++++++------------- 4 files changed, 559 insertions(+), 380 deletions(-) create mode 100644 docs/Builtin.rst diff --git a/docs/Builtin.rst b/docs/Builtin.rst new file mode 100644 index 000000000..2d938e30f --- /dev/null +++ b/docs/Builtin.rst @@ -0,0 +1,269 @@ +.. _built-in-commands: + +Built-in Commands +================= +The following commands are provided by the 'core' components of DFHack, rather +than plugins or scripts. + +.. contents:: + :local: + +.. _alias: + +alias +----- +The ``alias`` command allows configuring aliases to other DFHack commands. +Aliases are resolved immediately after built-in commands, which means that an +alias cannot override a built-in command, but can override a command implemented +by a plugin or script. + +Usage: + +:``alias list``: lists all configured aliases +:``alias add [arguments...]``: adds an alias +:``alias replace [arguments...]``: replaces an existing + alias with a new command, or adds the alias if it does not already exist +:``alias delete ``: removes the specified alias + +Aliases can be given additional arguments when created and invoked, which will +be passed to the underlying command in order. An example with +`devel/print-args`:: + + [DFHack]# alias add pargs devel/print-args example + [DFHack]# pargs text + example + text + + +.. _cls: + +cls +--- +Clear the terminal. Does not delete command history. + + +.. _die: + +die +--- +Instantly kills DF without saving. + + +.. _disable: +.. _enable: + +enable +------ +Many plugins and scripts can be in a distinct enabled or disabled state. Some of +them activate and deactivate automatically depending on the contents of the +world raws. Others store their state in world data. However a number of them +have to be enabled globally, and the init file is the right place to do it. + +Most such plugins or scripts support the built-in ``enable`` and ``disable`` +commands. Calling them at any time without arguments prints a list of enabled +and disabled plugins, and shows whether that can be changed through the same +commands. Passing plugin names to these commands will enable or disable the +specified plugins. For example, to enable the `manipulator` plugin:: + + enable manipulator + +It is also possible to enable or disable multiple plugins at once:: + + enable manipulator search + + +.. _fpause: + +fpause +------ +Forces DF to pause. This is useful when your FPS drops below 1 and you lose +control of the game. + + +.. _help: + +help +---- +Most commands support using the ``help `` built-in command to retrieve +further help without having to look online. ``? `` and ``man `` are +aliases. + +Some commands (including many scripts) instead take ``help`` or ``?`` as an +option on their command line - ie `` help``. + + +.. _hide: + +hide +---- +Hides the DFHack terminal window. Only available on Windows. + + +.. _keybinding: + +keybinding +---------- +To set keybindings, use the built-in ``keybinding`` command. Like any other +command it can be used at any time from the console, but bindings are not +remembered between runs of the game unless re-created in `dfhack.init`. + +Currently, any combinations of Ctrl/Alt/Shift with A-Z, 0-9, or F1-F12 are +supported. + +Possible ways to call the command: + +``keybinding list `` + List bindings active for the key combination. +``keybinding clear ...`` + Remove bindings for the specified keys. +``keybinding add "cmdline" "cmdline"...`` + Add bindings for the specified key. +``keybinding set "cmdline" "cmdline"...`` + Clear, and then add bindings for the specified key. + +The ```` parameter above has the following *case-sensitive* syntax:: + + [Ctrl-][Alt-][Shift-]KEY[@context[|context...]] + +where the *KEY* part can be any recognized key and [] denote optional parts. + +When multiple commands are bound to the same key combination, DFHack selects +the first applicable one. Later ``add`` commands, and earlier entries within one +``add`` command have priority. Commands that are not specifically intended for +use as a hotkey are always considered applicable. + +The ``context`` part in the key specifier above can be used to explicitly +restrict the UI state where the binding would be applicable. If called without +parameters, the ``keybinding`` command among other things prints the current +context string. + +Only bindings with a ``context`` tag that either matches the current context +fully, or is a prefix ending at a ``/`` boundary would be considered for +execution, i.e. when in context ``foo/bar/baz``, keybindings restricted to any +of ``@foo/bar/baz``, ``@foo/bar``, ``@foo`` or none will be active. + +Multiple contexts can be specified by separating them with a pipe (``|``) - for +example, ``@foo|bar|baz/foo`` would match anything under ``@foo``, ``@bar``, or +``@baz/foo``. + +Interactive commands like `liquids` cannot be used as hotkeys. + + +.. _kill-lua: + +kill-lua +-------- +Stops any currently-running Lua scripts. By default, scripts can only be +interrupted every 256 instructions. Use ``kill-lua force`` to interrupt the next +instruction. + + +.. _load: +.. _unload: +.. _reload: + +load +---- +``load``, ``unload``, and ``reload`` control whether a plugin is loaded into +memory - note that plugins are loaded but disabled unless you explicitly enable +them. Usage:: + + load|unload|reload PLUGIN|(-a|--all) + +Allows dealing with plugins individually by name, or all at once. + +Note that plugins do not maintain their enabled state if they are reloaded, so +you may need to use `enable` to re-enable a plugin after reloading it. + + +.. _ls: +.. _dir: + +ls +-- +``ls`` (or ``dir``) does not list files like the Unix command, but rather +available commands. In order to group related commands, each command is +associated with a list of tags. You can filter the listed commands by a +tag or a substring of the command name. Usage: + +:``ls``: Lists all available commands and the tags associated with them + (if any). +:``ls TAG``: Shows only commands that have the given tag. Use the `tags` command + to see the list of available tags. +:``ls STRING``: Shows commands that include the given string. E.g. ``ls auto`` + will show all the commands with "auto" in their names. If the string is also + the name of a tag, then it will be interpreted as a tag name. + +You can also pass some optional parameters to change how ``ls`` behaves: + +:``--notags``: Don't print out the tags associated with each command. +:``--dev``: Include commands intended for developers and modders. + + +.. _plug: + +plug +---- +Lists available plugins and whether they are enabled. + +``plug`` + Lists available plugins (*not* commands implemented by plugins) +``plug [PLUGIN] [PLUGIN] ...`` + List state and detailed description of the given plugins, + including commands implemented by the plugin. + + +.. _sc-script: + +sc-script +--------- +Allows additional scripts to be run when certain events occur (similar to +onLoad\*.init scripts) + + +.. _script: + +script +------ +Reads a text file, and runs each line as a DFHack command as if it had been +typed in by the user - treating the input like `an init file `. + +Some other tools, such as `autobutcher` and `workflow`, export their settings as +the commands to create them - which can later be reloaded with ``script``. + + +.. _show: + +show +---- +Shows the terminal window after it has been `hidden `. Only available on +Windows. You'll need to use it from a `keybinding` set beforehand, or the +in-game `command-prompt`. + + +.. _tags: + +tags +---- + +List the strings that the DFHack tools can be tagged with. You can find groups +of related tools by passing the tag name to `ls`. + +.. _type: + +type +---- +``type command`` shows where ``command`` is implemented. + +Other Commands +-------------- +The following commands are *not* built-in, but offer similarly useful functions. + +* `command-prompt` +* `hotkeys` +* `lua` +* `multicmd` +* `nopause` +* `quicksave` +* `rb` +* `repeat` diff --git a/docs/Core.rst b/docs/Core.rst index fe881a54d..ffa620f55 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -11,22 +11,23 @@ DFHack Core Command Implementation ====================== -DFHack commands can be implemented in three ways, all of which -are used in the same way: +DFHack commands can be implemented in any of three ways: :builtin: commands are implemented by the core of DFHack. They manage other DFHack tools, interpret commands, and control basic - aspects of DF (force pause or quit). + aspects of DF (force pause or quit). They are documented + `here `. :plugins: are stored in ``hack/plugins/`` and must be compiled with the same version of DFHack. They are less flexible than scripts, but used for complex or ongoing tasks because they run faster. + Plugins included with DFHack are documented `here `. :scripts: are Ruby or Lua scripts stored in ``hack/scripts/``. Because they don't need to be compiled, scripts are more flexible about versions, and easier to distribute. - Most third-party DFHack addons are scripts. - + Most third-party DFHack addons are scripts. All scripts included + with DFHack are documented `here `. Using DFHack Commands ===================== @@ -113,259 +114,6 @@ second (Windows) example uses `kill-lua` to stop a Lua script. you have multiple copies of DF running simultaneously. To assign a different port, see `remote-server-config`. - -Built-in Commands -================= -The following commands are provided by the 'core' components -of DFHack, rather than plugins or scripts. - -.. contents:: - :local: - - -.. _alias: - -alias ------ -The ``alias`` command allows configuring aliases to other DFHack commands. -Aliases are resolved immediately after built-in commands, which means that an -alias cannot override a built-in command, but can override a command implemented -by a plugin or script. - -Usage: - -:``alias list``: lists all configured aliases -:``alias add [arguments...]``: adds an alias -:``alias replace [arguments...]``: replaces an existing - alias with a new command, or adds the alias if it does not already exist -:``alias delete ``: removes the specified alias - -Aliases can be given additional arguments when created and invoked, which will -be passed to the underlying command in order. An example with `devel/print-args`:: - - [DFHack]# alias add pargs devel/print-args example - [DFHack]# pargs text - example - text - - -.. _cls: - -cls ---- -Clear the terminal. Does not delete command history. - - -.. _die: - -die ---- -Instantly kills DF without saving. - - -.. _disable: - -.. _enable: - -enable ------- -Many plugins can be in a distinct enabled or disabled state. Some of -them activate and deactivate automatically depending on the contents -of the world raws. Others store their state in world data. However a -number of them have to be enabled globally, and the init file is the -right place to do it. - -Most such plugins or scripts support the built-in ``enable`` and ``disable`` -commands. Calling them at any time without arguments prints a list -of enabled and disabled plugins, and shows whether that can be changed -through the same commands. Passing plugin names to these commands will enable -or disable the specified plugins. For example, to enable the `manipulator` -plugin:: - - enable manipulator - -It is also possible to enable or disable multiple plugins at once:: - - enable manipulator search - - -.. _fpause: - -fpause ------- -Forces DF to pause. This is useful when your FPS drops below 1 and you lose -control of the game. - - -.. _help: - -help ----- -Most commands support using the ``help `` built-in command -to retrieve further help without having to look at this document. -``? `` and ``man `` are aliases. - -Some commands (including many scripts) instead take ``help`` or ``?`` -as an option on their command line - ie `` help``. - - -.. _hide: - -hide ----- -Hides the DFHack terminal window. Only available on Windows. - - -.. _keybinding: - -keybinding ----------- -To set keybindings, use the built-in ``keybinding`` command. Like any other -command it can be used at any time from the console, but bindings are not -remembered between runs of the game unless re-created in `dfhack.init`. - -Currently, any combinations of Ctrl/Alt/Shift with A-Z, 0-9, or F1-F12 are supported. - -Possible ways to call the command: - -``keybinding list `` - List bindings active for the key combination. -``keybinding clear ...`` - Remove bindings for the specified keys. -``keybinding add "cmdline" "cmdline"...`` - Add bindings for the specified key. -``keybinding set "cmdline" "cmdline"...`` - Clear, and then add bindings for the specified key. - -The ```` parameter above has the following *case-sensitive* syntax:: - - [Ctrl-][Alt-][Shift-]KEY[@context[|context...]] - -where the *KEY* part can be any recognized key and [] denote optional parts. - -When multiple commands are bound to the same key combination, DFHack selects -the first applicable one. Later ``add`` commands, and earlier entries within one -``add`` command have priority. Commands that are not specifically intended for use -as a hotkey are always considered applicable. - -The ``context`` part in the key specifier above can be used to explicitly restrict -the UI state where the binding would be applicable. If called without parameters, -the ``keybinding`` command among other things prints the current context string. - -Only bindings with a ``context`` tag that either matches the current context fully, -or is a prefix ending at a ``/`` boundary would be considered for execution, i.e. -when in context ``foo/bar/baz``, keybindings restricted to any of ``@foo/bar/baz``, -``@foo/bar``, ``@foo`` or none will be active. - -Multiple contexts can be specified by separating them with a -pipe (``|``) - for example, ``@foo|bar|baz/foo`` would match -anything under ``@foo``, ``@bar``, or ``@baz/foo``. - -Interactive commands like `liquids` cannot be used as hotkeys. - - -.. _kill-lua: - -kill-lua --------- -Stops any currently-running Lua scripts. By default, scripts can -only be interrupted every 256 instructions. Use ``kill-lua force`` -to interrupt the next instruction. - - -.. _load: -.. _unload: -.. _reload: - -load ----- -``load``, ``unload``, and ``reload`` control whether a plugin is loaded -into memory - note that plugins are loaded but disabled unless you do -something. Usage:: - - load|unload|reload PLUGIN|(-a|--all) - -Allows dealing with plugins individually by name, or all at once. - -Note that plugins do not maintain their enabled state if they are reloaded, so -you may need to use `enable` to re-enable a plugin after reloading it. - - -.. _ls: - -ls --- -``ls`` does not list files like the Unix command, but rather -available commands - first built in commands, then plugins, -and scripts at the end. Usage: - -:ls -a: Also list scripts in subdirectories of ``hack/scripts/``, - which are generally not intended for direct use. -:ls : List subcommands for the given plugin. - - -.. _plug: - -plug ----- -Lists available plugins, including their state and detailed description. - -``plug`` - Lists available plugins (*not* commands implemented by plugins) -``plug [PLUGIN] [PLUGIN] ...`` - List state and detailed description of the given plugins, - including commands implemented by the plugin. - - -.. _sc-script: - -sc-script ---------- -Allows additional scripts to be run when certain events occur -(similar to onLoad*.init scripts) - - -.. _script: - -script ------- -Reads a text file, and runs each line as a DFHack command -as if it had been typed in by the user - treating the -input like `an init file `. - -Some other tools, such as `autobutcher` and `workflow`, export -their settings as the commands to create them - which are later -loaded with ``script`` - - -.. _show: - -show ----- -Shows the terminal window after it has been `hidden `. -Only available on Windows. You'll need to use it from a -`keybinding` set beforehand, or the in-game `command-prompt`. - -.. _type: - -type ----- -``type command`` shows where ``command`` is implemented. - -Other Commands --------------- -The following commands are *not* built-in, but offer similarly useful functions. - -* `command-prompt` -* `hotkeys` -* `lua` -* `multicmd` -* `nopause` -* `quicksave` -* `rb` -* `repeat` - - .. _dfhack-config: Configuration Files diff --git a/index.rst b/index.rst index 3e761bbc6..3b46699a7 100644 --- a/index.rst +++ b/index.rst @@ -30,6 +30,7 @@ User Manual /docs/Installing /docs/Support /docs/Core + /docs/Builtin /docs/Plugins /docs/Scripts /docs/Tags diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 8a27abad8..2999ee67e 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -1,80 +1,122 @@ --- The help text database. +-- The help text database and query interface. -- --- Command help is read from the the following sources: --- 1. rendered text in hack/docs/docs/ --- 2. (for scripts) the script sources if no pre-rendered text exists or if the --- script file has a modification time that is more recent than the --- pre-rendered text --- 3. (for plugins) the string passed to the PluginCommand initializer if no --- pre-rendered text exists +-- Help text is read from the rendered text in hack/docs/docs/. If no rendered +-- text exists, it is read from the script sources (for scripts) or the string +-- passed to the PluginCommand initializer (for plugins). -- --- For plugins that don't register any commands, the plugin name serves as the --- command so documentation on what happens when you enable the plugin can be --- found. +-- For plugins that don't register a command with the same name as the plugin, +-- the plugin name is registered as a separate entry so documentation on what +-- happens when you enable the plugin can be found. +-- +-- The database is lazy-loaded when an API method is called. It rechecks its +-- help sources for updates if an API method has not been called in the last +-- 60 seconds. local _ENV = mkmodule('helpdb') +-- paths local RENDERED_PATH = 'hack/docs/docs/tools/' +local BUILTIN_HELP = 'hack/docs/docs/Builtin.txt' local TAG_DEFINITIONS = 'hack/docs/docs/Tags.txt' +-- used when reading help text embedded in script sources local SCRIPT_DOC_BEGIN = '[====[' local SCRIPT_DOC_END = ']====]' +local SCRIPT_DOC_BEGIN_RUBY = '=begin' +local SCRIPT_DOC_END_RUBY = '=end' + +local ENTRY_TYPES = { + BUILTIN='builtin', + PLUGIN='plugin', + COMMAND='command' +} -local SOURCES = { +local HELP_SOURCES = { STUB='stub', RENDERED='rendered', PLUGIN='plugin', SCRIPT='script', } --- command name -> {short_help, long_help, tags, source, source_timestamp} --- also includes a script_source_path element if the source is a script --- and a unrunnable boolean if the source is a plugin that does not provide any --- commands to invoke directly. +-- entry name -> { +-- entry_types (set of ENTRY_TYPES), +-- short_help (string), +-- long_help (string), +-- tags (set), +-- help_source (element of HELP_SOURCES), +-- source_timestamp (mtime, 0 for non-files), +-- source_path (string, nil for non-files) +-- } +-- +-- entry_types is a set because plugin commands can also be the plugin names. db = db or {} --- tag name -> list of command names +-- tag name -> list of entry names +-- Tags defined in the TAG_DEFINITIONS file that have no associated db entries +-- will have an empty list. tag_index = tag_index or {} -local function get_rendered_path(command) - return RENDERED_PATH .. command .. '.txt' +local function get_rendered_path(entry_name) + return RENDERED_PATH .. entry_name .. '.txt' end -local function has_rendered_help(command) - return dfhack.filesystem.mtime(get_rendered_path(command)) ~= -1 +local function has_rendered_help(entry_name) + return dfhack.filesystem.mtime(get_rendered_path(entry_name)) ~= -1 end local DEFAULT_HELP_TEMPLATE = [[ %s %s -Tags: None - No help available. ]] - -local function make_default_entry(command, source) +local function make_default_entry(entry_name, entry_types, source, + source_timestamp, source_path) local default_long_help = DEFAULT_HELP_TEMPLATE:format( - command, ('*'):rep(#command)) - return {short_help='No help available.', long_help=default_long_help, - tags={}, source=source, source_timestamp=0} + entry_name, ('*'):rep(#entry_name)) + return { + entry_types=entry_types, + short_help='No help available.', + long_help=default_long_help, + tags={}, + help_source=source, + source_timestamp=source_timestamp or 0, + source_path=source_path} end --- updates the short_text, the long_text, and the tags in the given entry +-- updates the short_text, the long_text, and the tags in the given entry based +-- on the text returned from the iterator. +-- if defined, opts can have the following fields: +-- begin_marker (string that marks the beginning of the help text; all text +-- before this marker is ignored) +-- end_marker (string that marks the end of the help text; text will stop +-- being parsed after this marker is seen) +-- no_header (don't try to find the entity name at the top of the help text) +-- first_line_is_short_help (read the short help text from the first commented +-- line of the script instead of using the first +-- sentence of the long help text) local function update_entry(entry, iterator, opts) opts = opts or {} local lines = {} + local first_line_is_short_help = opts.first_line_is_short_help local begin_marker_found,header_found = not opts.begin_marker,opts.no_header local tags_found, short_help_found, in_short_help = false, false, false for line in iterator do - if not short_help_found and opts.first_line_is_short_help then - local _,_,text = line:trim():find('^%-%-%s*(.*)') - if not text:endswith('.') then - text = text .. '.' + if not short_help_found and first_line_is_short_help then + line = line:trim() + local _,_,text = line:find('^%-%-%s*(.*)') or line:find('^#%s*(.*)') + if not text then + -- if no first-line short help found, fall back to getting the + -- first sentence of the help text. + first_line_is_short_help = false + else + if not text:endswith('.') then + text = text .. '.' + end + entry.short_help = text + short_help_found = true + goto continue end - entry.short_help = text - short_help_found = true - goto continue end if not begin_marker_found then local _, endpos = line:find(opts.begin_marker, 1, true) @@ -119,80 +161,131 @@ local function update_entry(entry, iterator, opts) entry.long_help = table.concat(lines, '\n') end -local function make_rendered_entry(old_entry, command) - local rendered_path = get_rendered_path(command) +-- create db entry based on parsing sphinx-rendered help text +local function make_rendered_entry(old_entry, entry_name, entry_types) + local rendered_path = get_rendered_path(entry_name) local source_timestamp = dfhack.filesystem.mtime(rendered_path) - if old_entry and old_entry.source == SOURCES.RENDERED and + if old_entry and old_entry.source == HELP_SOURCES.RENDERED and old_entry.source_timestamp >= source_timestamp then -- we already have the latest info return old_entry end - local entry = make_default_entry(command, SOURCES.RENDERED) + local entry = make_default_entry(entry_name, entry_types, + HELP_SOURCES.RENDERED, source_timestamp, rendered_path) update_entry(entry, io.lines(rendered_path)) - entry.source_timestamp = source_timestamp return entry end -local function make_plugin_entry(old_entry, command) - if old_entry and old_entry.source == SOURCES.PLUGIN then +-- create db entry based on the help text in the plugin source (used by +-- out-of-tree plugins) +local function make_plugin_entry(old_entry, entry_name, entry_types) + if old_entry and old_entry.source == HELP_SOURCES.PLUGIN then -- we can't tell when a plugin is reloaded, so we can either choose to -- always refresh or never refresh. let's go with never for now for -- performance. return old_entry end - local entry = make_default_entry(command, SOURCES.PLUGIN) - local long_help = dfhack.internal.getCommandHelp(command) + local entry = make_default_entry(entry_name, entry_types, + HELP_SOURCES.PLUGIN) + local long_help = dfhack.internal.getCommandHelp(entry_name) if long_help and #long_help:trim() > 0 then update_entry(entry, long_help:trim():gmatch('[^\n]*'), {no_header=true}) end return entry end -local function make_script_entry(old_entry, command, script_source_path) +-- create db entry based on the help text in the script source (used by +-- out-of-tree scripts) +local function make_script_entry(old_entry, entry_name, script_source_path) local source_timestamp = dfhack.filesystem.mtime(script_source_path) - if old_entry and old_entry.source == SOURCES.SCRIPT and + if old_entry and old_entry.source == HELP_SOURCES.SCRIPT and old_entry.script_source_path == script_source_path and old_entry.source_timestamp >= source_timestamp then -- we already have the latest info return old_entry end - local entry = make_default_entry(command, SOURCES.SCRIPT) + local entry = make_default_entry(entry_name, {[ENTRY_TYPES.COMMAND]=true}, + HELP_SOURCES.SCRIPT, source_timestamp, script_source_path) + local is_rb = script_source_path:endswith('.rb') update_entry(entry, io.lines(script_source_path), - {begin_marker=SCRIPT_DOC_BEGIN, end_marker=SCRIPT_DOC_END, - first_line_is_short_help=true}) - entry.source_timestamp = source_timestamp + {begin_marker=(is_rb and SCRIPT_DOC_BEGIN_RUBY or SCRIPT_DOC_BEGIN), + end_marker=(is_rb and SCRIPT_DOC_BEGIN_RUBY or SCRIPT_DOC_END), + first_line_is_short_help=true}) return entry end -local function update_db(old_db, db, source, command, flags) - if db[command] then +-- updates the db (and associated tag index) with a new entry if the entry_name +-- doesn't already exist in the db. +local function update_db(old_db, db, source, entry_name, kwargs) + if db[entry_name] then -- already in db (e.g. from a higher-priority script dir); skip return end - local entry, old_entry = nil, old_db[command] - if source == SOURCES.RENDERED then - entry = make_rendered_entry(old_entry, command) - elseif source == SOURCES.PLUGIN then - entry = make_plugin_entry(old_entry, command) - elseif source == SOURCES.SCRIPT then - entry = make_script_entry(old_entry, command, flags.script_source) - elseif source == SOURCES.STUB then - entry = make_default_entry(command, SOURCES.STUB) + local entry, old_entry = nil, old_db[entry_name] + if source == HELP_SOURCES.RENDERED then + entry = make_rendered_entry(old_entry, entry_name, kwargs.entry_types) + elseif source == HELP_SOURCES.PLUGIN then + entry = make_plugin_entry(old_entry, entry_name, kwargs.entry_types) + elseif source == HELP_SOURCES.SCRIPT then + entry = make_script_entry(old_entry, entry_name, kwargs.script_source) + elseif source == HELP_SOURCES.STUB then + entry = make_default_entry(entry_name, kwargs.entry_types, + HELP_SOURCES.STUB) else error('unhandled help source: ' .. source) end - - entry.unrunnable = (flags or {}).unrunnable - - db[command] = entry + db[entry_name] = entry for _,tag in ipairs(entry.tags) do - -- unknown tags are ignored + -- ignore unknown tags if tag_index[tag] then - table.insert(tag_index[tag], command) + table.insert(tag_index[tag], entry_name) end end end +local BUILTINS = { + alias='Configure helper aliases for other DFHack commands.', + cls='Clear the console screen.', + clear='Clear the console screen.', + die='Force DF to close immediately, without saving.', + enable='Enable a plugin or persistent script.', + disable='Disable a plugin or persistent script.', + fpause='Force DF to pause.', + help='Usage help for the given plugin, command, or script.', + hide='Hide the terminal window (Windows only).', + keybinding='Modify bindings of commands to in-game key shortcuts.', + ['kill-lua']='Stop a misbehaving Lua script.', + ['load']='Load and register a plugin library.', + unload='Unregister and unload a plugin.', + reload='Unload and reload a plugin library.', + ls='List commands, optionally filtered by a tag or substring.', + dir='List commands, optionally filtered by a tag or substring.', + plug='List plugins and whether they are enabled.', + ['sc-script']='Automatically run specified scripts on state change events.', + script='Run commands specified in a file.', + show='Show a hidden terminal window (Windows only).', + tags='List the tags that the DFHack tools are grouped by.', + ['type']='Discover how a command is implemented.', +} + +-- add the builtin commands to the db +local function scan_builtins(old_db, db) + local entry = make_default_entry('builtin', + {[ENTRY_TYPES.BUILTIN]=true, [ENTRY_TYPES.COMMAND]=true}, + HELP_SOURCES.RENDERED, 0, BUILTIN_HELP) + -- read in builtin help + local f = io.open(BUILTIN_HELP) + if f then + entry.long_help = f:read('*all') + end + for b,short_help in pairs(BUILTINS) do + local builtin_entry = copyall(entry) + builtin_entry.short_help = short_help + db[b] = builtin_entry + end +end + +-- scan for plugins and plugin-provided commands and add their help to the db local function scan_plugins(old_db, db) local plugin_names = dfhack.internal.listPlugins() for _,plugin in ipairs(plugin_names) do @@ -202,36 +295,41 @@ local function scan_plugins(old_db, db) -- documentation to update_db(old_db, db, has_rendered_help(plugin) and - SOURCES.RENDERED or SOURCES.STUB, - plugin, {unrunnable=true}) + HELP_SOURCES.RENDERED or HELP_SOURCES.STUB, + plugin, {entry_types={[ENTRY_TYPES.PLUGIN]=true}}) goto continue end for _,command in ipairs(commands) do + local entry_types = {[ENTRY_TYPES.COMMAND]=true} + if command == plugin then + entry_types[ENTRY_TYPES.PLUGIN]=true + end update_db(old_db, db, has_rendered_help(command) and - SOURCES.RENDERED or SOURCES.PLUGIN, - command) + HELP_SOURCES.RENDERED or HELP_SOURCES.PLUGIN, + command, {entry_types=entry_types}) end ::continue:: end end +-- scan for scripts and add their help to the db local function scan_scripts(old_db, db) for _,script_path in ipairs(dfhack.internal.getScriptPaths()) do local files = dfhack.filesystem.listdir_recursive( script_path, nil, false) if not files then goto skip_path end for _,f in ipairs(files) do - if f.isdir or not f.path:endswith('.lua') or + if f.isdir or + (not f.path:endswith('.lua') and not f.path:endswith('.rb')) or f.path:startswith('test/') or f.path:startswith('internal/') then goto continue end local script_source = script_path .. '/' .. f.path - local script_is_newer = dfhack.filesystem.mtime(script_source) > - dfhack.filesystem.mtime(get_rendered_path(f.path)) update_db(old_db, db, - script_is_newer and SOURCES.SCRIPT or SOURCES.RENDERED, + has_rendered_help(f.path) and + HELP_SOURCES.RENDERED or HELP_SOURCES.SCRIPT, f.path:sub(1, #f.path - 4), {script_source=script_source}) ::continue:: end @@ -239,6 +337,8 @@ local function scan_scripts(old_db, db) end end +-- read tags and descriptions from the TAG_DEFINITIONS file and add them all +-- to tag_index, initizlizing each entry with an empty list. local function initialize_tags() local tag, desc, in_desc = nil, nil, false for line in io.lines(TAG_DEFINITIONS) do @@ -261,7 +361,7 @@ local function initialize_tags() end -- ensures the db is up to date by scanning all help sources. does not do --- anything if it has already been run within the last 10 seconds. +-- anything if it has already been run within the last 60 seconds. last_refresh_ms = last_refresh_ms or 0 local function ensure_db() local now_ms = dfhack.getTickCount() @@ -272,16 +372,21 @@ local function ensure_db() db, tag_index = {}, {} initialize_tags() + scan_builtins(old_db, db) scan_plugins(old_db, db) scan_scripts(old_db, db) end -local function get_db_property(command, property) +--------------------------------------------------------------------------- +-- get API +--------------------------------------------------------------------------- + +local function get_db_property(entry_name, property) ensure_db() - if not db[command] then - error(('command not found: "%s"'):format(command)) + if not db[entry_name] then + error(('entry not found: "%s"'):format(entry_name)) end - return db[command][property] + return db[entry_name][property] end -- returns the ~54 char summary blurb associated with the entry @@ -308,6 +413,32 @@ function get_entry_tags(entry) return set_to_sorted_list(get_db_property(entry, 'tags')) end +-- returns whether the given string matches a tag name +function is_tag(str) + ensure_db() + return not not tag_index[str] +end + +-- returns the defined tags in alphabetical order +function get_tags() + ensure_db() + return set_to_sorted_list(tag_index) +end + +-- returns the description associated with the given tag +function get_tag_description(tag) + ensure_db() + if not tag_index[tag] then + error('invalid tag: ' .. tag) + end + return tag_index[tag].description +end + +--------------------------------------------------------------------------- +-- search API +--------------------------------------------------------------------------- + +-- returns a list of path elements in reverse order local function chunk_for_sorting(str) local parts = str:split('/') local chunks = {} @@ -336,11 +467,8 @@ local function sort_by_basename(a, b) return false end -local function matches(command, filter) - local db_entry = db[command] - if filter.runnable and db_entry.unrunnable then - return false - end +local function matches(entry_name, filter) + local db_entry = db[entry_name] if filter.tag then local matched = false for _,tag in ipairs(filter.tag) do @@ -353,10 +481,22 @@ local function matches(command, filter) return false end end + if filter.types then + local matched = false + for _,etype in ipairs(filter.types) do + if db_entry.entry_types[etype] then + matched = true + break + end + end + if not matched then + return false + end + end if filter.str then local matched = false for _,str in ipairs(filter.str) do - if command:find(str, 1, true) then + if entry_name:find(str, 1, true) then matched = true break end @@ -368,6 +508,7 @@ local function matches(command, filter) return true end +-- converts strings into single-element lists containing that string local function normalize_string_list(l) if not l then return nil end if type(l) == 'string' then @@ -376,28 +517,35 @@ local function normalize_string_list(l) return l end +-- normalizes the lists in the filter and returns nil if no filter elements are +-- populated local function normalize_filter(f) if not f then return nil end local filter = {} filter.str = normalize_string_list(f.str) filter.tag = normalize_string_list(f.tag) - filter.runnable = f.runnable - if not filter.str and not filter.tag and not filter.runnable then + filter.types = normalize_string_list(f.types) + if not filter.str and not filter.tag and not filter.types then return nil end return filter end --- returns a list of identifiers, alphabetized by their last path component --- (e.g. gui/autobutcher will immediately follow autobutcher). +-- returns a list of entry names, alphabetized by their last path component, +-- with populated path components coming before null path components (e.g. +-- autobutcher will immediately follow gui/autobutcher). -- the optional include and exclude filter params are maps with the following -- elements: -- str - if a string, filters by the given substring. if a table of strings, --- includes commands that match any of the given substrings. +-- includes entry names that match any of the given substrings. -- tag - if a string, filters by the given tag name. if a table of strings, --- includes commands that match any of the given tags. --- runnable - if true, matches only runnable commands, not plugin names. -function get_entries(include, exclude) +-- includes entries that match any of the given tags. +-- types - if a string, matches entries of the given type. if a table of +-- strings, includes entries that match any of the given types. valid +-- types are: "builtin", "plugin", "command". note that many plugin +-- commands have the same name as the plugin, so those entries will +-- match both "plugin" and "command" types. +function search_entries(include, exclude) ensure_db() include = normalize_filter(include) exclude = normalize_filter(exclude) @@ -412,6 +560,10 @@ function get_entries(include, exclude) return commands end +--------------------------------------------------------------------------- +-- list API (outputs to console) +--------------------------------------------------------------------------- + local function get_max_width(list, min_width) local width = min_width or 0 for _,item in ipairs(list) do @@ -420,43 +572,52 @@ local function get_max_width(list, min_width) return width end +-- prints the defined tags and their descriptions to the console +function list_tags() + local tags = get_tags() + local width = get_max_width(tags, 10) + for _,tag in ipairs(tags) do + print((' %-'..width..'s %s'):format(tag, get_tag_description(tag))) + end +end + -- prints the requested entries to the console. include and exclude filters are --- as in get_entries above. -function list_entries(include_tags, include, exclude) - local entries = get_entries(include, exclude) +-- defined as in search_entries() above. +function list_entries(skip_tags, include, exclude) + local entries = search_entries(include, exclude) local width = get_max_width(entries, 10) for _,entry in ipairs(entries) do - print((' %-'..width..'s %s'):format( + print((' %-'..width..'s %s'):format( entry, get_entry_short_help(entry))) - if include_tags then - print((' '..(' '):rep(width)..' tags(%s)'):format( - table.concat(get_entry_tags(entry), ','))) + if not skip_tags then + local tags = get_entry_tags(entry) + if #tags > 0 then + print((' tags: %s'):format(table.concat(tags, ', '))) + end end end -end - --- returns the defined tags in alphabetical order -function get_tags() - ensure_db() - return set_to_sorted_list(tag_index) -end - --- returns the description associated with the given tag -function get_tag_description(tag) - ensure_db() - if not tag_index[tag] then - error('invalid tag: ' .. tag) + if #entries == 0 then + print('no entries found.') end - return tag_index[tag].description end --- prints the defined tags and their descriptions to the console -function list_tags() - local tags = get_tags() - local width = get_max_width(tags, 10) - for _,tag in ipairs(tags) do - print((' %-'..width..'s %s'):format(tag, get_tag_description(tag))) +-- wraps the list_entries() API to provide a more convenient interface for Core +-- to implement the 'ls' builtin command. +-- filter_str - if a tag name, will filter by that tag. otherwise, will filter +-- as a substring +-- skip_tags - whether to skip printing tag info +-- show_dev_commands - if true, will include scripts in the modtools/ and +-- devel/ directories. otherwise those scripts will be +-- excluded +function ls(filter_str, skip_tags, show_dev_commands) + local include = {types={ENTRY_TYPES.COMMAND}} + if is_tag(filter_str) then + include.tag = filter_str + else + include.str = filter_str end + list_entries(skip_tags, include, + show_dev_commands and {} or {str={'modtools/', 'devel/'}}) end return _ENV From e899510b8bc3374896ae79575cb840ecc210e195 Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 8 Jul 2022 15:53:51 -0700 Subject: [PATCH 183/854] Use helpdb to implement help and ls built-in commands and dfhack.script_help() (#2242) * use helpdb to implement the help and ls builtins * use helpdb to implement dfhack.script_help() --- library/Core.cpp | 234 +++++++++++++++-------------------------- library/lua/dfhack.lua | 38 +------ library/lua/helpdb.lua | 35 ++++-- 3 files changed, 117 insertions(+), 190 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index aa43f3c2b..62fe366f1 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -446,6 +446,7 @@ command_result Core::runCommand(color_ostream &out, const std::string &command) static const std::set built_in_commands = { "ls" , "help" , + "tags" , "type" , "load" , "unload" , @@ -674,25 +675,71 @@ string getBuiltinCommand(std::string cmd) return builtin; } -void ls_helper(color_ostream &con, const string &name, const string &desc) -{ - const size_t help_line_length = 80 - 22 - 5; - const string padding = string(80 - help_line_length, ' '); - vector lines; - con.print(" %-22s - ", name.c_str()); - word_wrap(&lines, desc, help_line_length); +void help_helper(color_ostream &con, const string &entry_name) { + CoreSuspender suspend; + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 2) || + !Lua::PushModulePublic(con, L, "helpdb", "help")) { + con.printerr("Failed to load helpdb Lua code\n"); + return; + } + + Lua::Push(L, entry_name); - // print first line, then any additional lines preceded by padding - for (size_t i = 0; i < lines.size(); i++) - con.print("%s%s\n", i ? padding.c_str() : "", lines[i].c_str()); + if (!Lua::SafeCall(con, L, 1, 0)) { + con.printerr("Failed Lua call to helpdb.help.\n"); + } } -void ls_helper(color_ostream &con, const PluginCommand &pcmd) -{ - if (pcmd.isHotkeyCommand()) - con.color(COLOR_CYAN); - ls_helper(con, pcmd.name, pcmd.description); - con.reset_color(); +void tags_helper(color_ostream &con) { + CoreSuspender suspend; + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 1) || + !Lua::PushModulePublic(con, L, "helpdb", "tags")) { + con.printerr("Failed to load helpdb Lua code\n"); + return; + } + + if (!Lua::SafeCall(con, L, 0, 0)) { + con.printerr("Failed Lua call to helpdb.tags.\n"); + } +} + +void ls_helper(color_ostream &con, const vector ¶ms) { + vector filter; + bool skip_tags = false; + bool show_dev_commands = false; + + for (auto str : params) { + if (str == "--notags") + skip_tags = true; + else if (str == "--dev") + show_dev_commands = true; + else + filter.push_back(str); + } + + CoreSuspender suspend; + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 4) || + !Lua::PushModulePublic(con, L, "helpdb", "ls")) { + con.printerr("Failed to load helpdb Lua code\n"); + return; + } + + Lua::PushVector(L, filter); + Lua::Push(L, skip_tags); + Lua::Push(L, show_dev_commands); + + if (!Lua::SafeCall(con, L, 3, 0)) { + con.printerr("Failed Lua call to helpdb.ls.\n"); + } } command_result Core::runCommand(color_ostream &con, const std::string &first_, vector &parts) @@ -733,70 +780,33 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v "On Windows, you may have to resize your console window. The appropriate menu is accessible\n" "by clicking on the program icon in the top bar of the window.\n\n"); } - con.print("Basic commands:\n" - " help|?|man - This text.\n" - " help COMMAND - Usage help for the given command.\n" - " ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" - " cls|clear - Clear the console.\n" - " fpause - Force DF to pause.\n" - " die - Force DF to close immediately\n" - " keybinding - Modify bindings of commands to keys\n" - "Plugin management (useful for developers):\n" - " plug [PLUGIN|v] - List plugin state and description.\n" - " load PLUGIN|-all - Load a plugin by name or load all possible plugins.\n" - " unload PLUGIN|-all - Unload a plugin or all loaded plugins.\n" - " reload PLUGIN|-all - Reload a plugin or all loaded plugins.\n" + con.print("Here are some basic commands to get you started:\n" + " help|?|man - This text.\n" + " help - Usage help for the given plugin, command, or script.\n" + " tags - List the tags that the DFHack tools are grouped by.\n" + " ls|dir [] - List commands, optionally filtered by a tag or substring.\n" + " Optional parameters:\n" + " --notags: skip printing tags for each command.\n" + " --dev: include commands intended for developers and modders.\n" + " cls|clear - Clear the console.\n" + " fpause - Force DF to pause.\n" + " die - Force DF to close immediately, without saving.\n" + " keybinding - Modify bindings of commands to in-game key shortcuts.\n" + "\n" + "See more commands by running 'ls'.\n\n" ); - con.print("\nDFHack version %s\n", dfhack_version_desc().c_str()); - } - else if (parts.size() == 1) - { - if (getBuiltinCommand(parts[0]).size()) - { - con << parts[0] << ": built-in command; Use `ls`, `help`, or check hack/Readme.html for more information" << std::endl; - return CR_NOT_IMPLEMENTED; - } - Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); - if (plug) { - for (size_t j = 0; j < plug->size();j++) - { - const PluginCommand & pcmd = (plug->operator[](j)); - if (pcmd.name != parts[0]) - continue; - - if (pcmd.isHotkeyCommand()) - con.color(COLOR_CYAN); - con.print("%s: %s\n",pcmd.name.c_str(), pcmd.description.c_str()); - con.reset_color(); - if (!pcmd.usage.empty()) - con << "Usage:\n" << pcmd.usage << flush; - return CR_OK; - } - } - string file = findScript(parts[0] + ".lua"); - if ( file != "" ) { - string help = getScriptHelp(file, "--"); - con.print("%s: %s\n", parts[0].c_str(), help.c_str()); - return CR_OK; - } - if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) { - file = findScript(parts[0] + ".rb"); - if ( file != "" ) { - string help = getScriptHelp(file, "#"); - con.print("%s: %s\n", parts[0].c_str(), help.c_str()); - return CR_OK; - } - } - con.printerr("Unknown command: %s\n", parts[0].c_str()); - return CR_FAILURE; + con.print("DFHack version %s\n", dfhack_version_desc().c_str()); } else { - con.printerr("not implemented yet\n"); - return CR_NOT_IMPLEMENTED; + help_helper(con, parts[0]); } } + else if (builtin == "tags") + { + tags_helper(con); + } else if (builtin == "load" || builtin == "unload" || builtin == "reload") { bool all = false; @@ -906,83 +916,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } else if (builtin == "ls" || builtin == "dir") { - bool all = false; - if (parts.size() && parts[0] == "-a") - { - all = true; - vector_erase_at(parts, 0); - } - if(parts.size()) - { - string & plugname = parts[0]; - const Plugin * plug = (*plug_mgr)[plugname]; - if(!plug) - { - con.printerr("There's no plugin called %s!\n", plugname.c_str()); - } - else if (plug->getState() != Plugin::PS_LOADED) - { - con.printerr("Plugin %s is not loaded.\n", plugname.c_str()); - } - else if (!plug->size()) - { - con.printerr("Plugin %s is loaded but does not implement any commands.\n", plugname.c_str()); - } - else for (size_t j = 0; j < plug->size();j++) - { - ls_helper(con, plug->operator[](j)); - } - } - else - { - con.print( - "builtin:\n" - " help|?|man - This text or help specific to a plugin.\n" - " ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" - " cls|clear - Clear the console.\n" - " fpause - Force DF to pause.\n" - " die - Force DF to close immediately\n" - " kill-lua - Stop an active Lua script\n" - " keybinding - Modify bindings of commands to keys\n" - " script FILENAME - Run the commands specified in a file.\n" - " sc-script - Automatically run specified scripts on state change events\n" - " plug [PLUGIN|v] - List plugin state and detailed description.\n" - " load PLUGIN|-all [...] - Load a plugin by name or load all possible plugins.\n" - " unload PLUGIN|-all [...] - Unload a plugin or all loaded plugins.\n" - " reload PLUGIN|-all [...] - Reload a plugin or all loaded plugins.\n" - " enable/disable PLUGIN [...] - Enable or disable a plugin if supported.\n" - " type COMMAND - Display information about where a command is implemented\n" - "\n" - "plugins:\n" - ); - std::set out; - for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) - { - const Plugin * plug = it->second; - if(!plug->size()) - continue; - for (size_t j = 0; j < plug->size();j++) - { - const PluginCommand & pcmd = (plug->operator[](j)); - out.insert(sortable(pcmd.isHotkeyCommand(),pcmd.name,pcmd.description)); - } - } - for(auto iter = out.begin();iter != out.end();iter++) - { - if ((*iter).recolor) - con.color(COLOR_CYAN); - ls_helper(con, iter->name, iter->description); - con.reset_color(); - } - std::map scripts; - listAllScripts(scripts, all); - if (!scripts.empty()) - { - con.print("\nscripts:\n"); - for (auto iter = scripts.begin(); iter != scripts.end(); ++iter) - ls_helper(con, iter->first, iter->second); - } - } + ls_helper(con, parts); } else if (builtin == "plug") { diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index e476ffa11..8af77e2e4 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -1,4 +1,4 @@ --- Common startup file for all dfhack plugins with lua support +-- Common startup file for all dfhack scripts and plugins with lua support -- The global dfhack table is already created by C++ init code. -- Setup the global environment. @@ -652,8 +652,9 @@ function Script:needs_update() return (not self.env) or self.mtime ~= dfhack.filesystem.mtime(self.path) end function Script:get_flags() - if self.flags_mtime ~= dfhack.filesystem.mtime(self.path) then - self.flags_mtime = dfhack.filesystem.mtime(self.path) + local mtime = dfhack.filesystem.mtime(self.path) + if self.flags_mtime ~= mtime then + self.flags_mtime = mtime self._flags = {} local f = io.open(self.path) local contents = f:read('*all') @@ -814,36 +815,7 @@ end function dfhack.script_help(script_name, extension) script_name = script_name or dfhack.current_script_name() - extension = extension or 'lua' - local full_name = script_name .. '.' .. extension - local path = dfhack.internal.findScript(script_name .. '.' .. extension) - or error("Could not find script: " .. full_name) - local begin_seq, end_seq - if extension == 'rb' then - begin_seq = '=begin' - end_seq = '=end' - else - begin_seq = '[====[' - end_seq = ']====]' - end - local f = io.open(path) or error("Could not open " .. path) - local in_help = false - local help = '' - for line in f:lines() do - if line:endswith(begin_seq) then - in_help = true - elseif in_help then - if line:endswith(end_seq) then - break - end - if line ~= script_name and line ~= ('='):rep(#script_name) then - help = help .. line .. '\n' - end - end - end - f:close() - help = help:gsub('^\n+', ''):gsub('\n+$', '') - return help + return require('helpdb').get_entry_long_help(script_name) end local function _run_command(args, use_console) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 2999ee67e..e72f0804d 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -326,11 +326,13 @@ local function scan_scripts(old_db, db) f.path:startswith('internal/') then goto continue end + local dot_index = f.path:find('%.[^.]*$') local script_source = script_path .. '/' .. f.path update_db(old_db, db, has_rendered_help(f.path) and HELP_SOURCES.RENDERED or HELP_SOURCES.SCRIPT, - f.path:sub(1, #f.path - 4), {script_source=script_source}) + f.path:sub(1, dot_index - 1), + {script_source=script_source}) ::continue:: end ::skip_path:: @@ -384,7 +386,7 @@ end local function get_db_property(entry_name, property) ensure_db() if not db[entry_name] then - error(('entry not found: "%s"'):format(entry_name)) + error(('helpdb entry not found: "%s"'):format(entry_name)) end return db[entry_name][property] end @@ -415,8 +417,17 @@ end -- returns whether the given string matches a tag name function is_tag(str) + if not str or #str == 0 then + return false + end ensure_db() - return not not tag_index[str] + if type(str) == "string" then str = {str} end + for _,s in ipairs(str) do + if not tag_index[s] then + return false + end + end + return true end -- returns the defined tags in alphabetical order @@ -510,7 +521,7 @@ end -- converts strings into single-element lists containing that string local function normalize_string_list(l) - if not l then return nil end + if not l or #l == 0 then return nil end if type(l) == 'string' then return {l} end @@ -561,9 +572,19 @@ function search_entries(include, exclude) end --------------------------------------------------------------------------- --- list API (outputs to console) +-- print API (outputs to console) --------------------------------------------------------------------------- +-- implements the 'help' builtin command +function help(entry) + ensure_db() + if not db[entry] then + dfhack.printerr(('No help entry found for "%s"'):format(entry)) + return + end + print(get_entry_long_help(entry)) +end + local function get_max_width(list, min_width) local width = min_width or 0 for _,item in ipairs(list) do @@ -572,8 +593,8 @@ local function get_max_width(list, min_width) return width end --- prints the defined tags and their descriptions to the console -function list_tags() +-- implements the 'tags' builtin command +function tags() local tags = get_tags() local width = get_max_width(tags, 10) for _,tag in ipairs(tags) do From 8d99b7e6e1f5d14ee5d1eb4c4ecea5750c4bddef Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 9 Jul 2022 23:01:31 -0700 Subject: [PATCH 184/854] prepare for plugin doc split --- .gitignore | 3 +- CMakeLists.txt | 2 +- conf.py | 139 +- docs/Core.rst | 11 +- docs/Dev-intro.rst | 7 +- docs/Documentation.rst | 8 +- docs/History.rst | 4 +- docs/Introduction.rst | 2 +- docs/Memory-research.rst | 2 +- docs/NEWS-dev.rst | 2 +- docs/NEWS.rst | 2 +- docs/Plugins.rst | 3436 ------------------- docs/Removed.rst | 8 + docs/Scripts.rst | 19 - docs/Tools.rst | 13 - docs/_auto/.gitignore | 1 - docs/build-pdf.sh | 11 +- docs/build.sh | 12 +- docs/{_changelogs => changelogs}/.gitignore | 1 + docs/guides/examples-guide.rst | 4 +- docs/index-tools.rst | 24 + docs/{tools => plugins}/cromulate.rst | 0 docs/sphinx_extensions/dfhack/changelog.py | 14 +- index.rst | 6 +- 24 files changed, 127 insertions(+), 3604 deletions(-) delete mode 100644 docs/Plugins.rst delete mode 100644 docs/Scripts.rst delete mode 100644 docs/Tools.rst delete mode 100644 docs/_auto/.gitignore rename docs/{_changelogs => changelogs}/.gitignore (50%) create mode 100644 docs/index-tools.rst rename docs/{tools => plugins}/cromulate.rst (100%) diff --git a/.gitignore b/.gitignore index 0b54f4a84..5155f2bb9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,10 +15,11 @@ build/VC2010 !build/ # Sphinx generated documentation -docs/_* +docs/changelogs/ docs/html/ docs/pdf/ docs/text/ +docs/tools/ # in-place build build/Makefile diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bee1c04a..46bd1917b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -505,7 +505,7 @@ if(BUILD_DOCS) DESTINATION ${DFHACK_USERDOC_DESTINATION}/docs) install(DIRECTORY ${dfhack_SOURCE_DIR}/docs/text/ DESTINATION ${DFHACK_USERDOC_DESTINATION}/docs) - install(FILES docs/_auto/news.rst docs/_auto/news-dev.rst DESTINATION ${DFHACK_USERDOC_DESTINATION}) + install(FILES docs/changelogs/news.rst docs/changelogs/news-dev.rst DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(FILES "README.html" DESTINATION "${DFHACK_DATA_DESTINATION}") endif() diff --git a/conf.py b/conf.py index 53b192467..d7be99a15 100644 --- a/conf.py +++ b/conf.py @@ -29,8 +29,6 @@ import sys from docutils import nodes from docutils.parsers.rst import roles -sphinx_major_version = sphinx.version_info[0] - def get_keybinds(root, files, keybindings): """Add keybindings in the specified files to the given keybindings dict. @@ -85,107 +83,86 @@ def dfhack_keybind_role_func(role, rawtext, text, lineno, inliner, roles.register_canonical_role('dfhack-keybind', dfhack_keybind_role_func) -# -- Autodoc for DFhack scripts ------------------------------------------- +# -- Autodoc for DFhack plugins and scripts ------------------------------- -def doc_dir(dirname, files): - """Yield (command, includepath) for each script in the directory.""" +def doc_dir(dirname, files, prefix): + """Yield (name, includepath) for each file in the directory.""" sdir = os.path.relpath(dirname, '.').replace('\\', '/').replace('../', '') + if prefix == '.': + prefix = '' + else: + prefix += '/' for f in files: - if f[-3:] not in ('lua', '.rb'): + if f[-4:] != '.rst': continue - with open(os.path.join(dirname, f), 'r', encoding='utf8') as fstream: - text = [l.rstrip() for l in fstream.readlines() if l.strip()] - # Some legacy lua files use the ruby tokens (in 3rdparty scripts) - tokens = ('=begin', '=end') - if f[-4:] == '.lua' and any('[====[' in line for line in text): - tokens = ('[====[', ']====]') - command = None - for line in text: - if command and line == len(line) * '=': - yield command, sdir + '/' + f, tokens[0], tokens[1] - break - command = line + yield prefix + f[:-4], sdir + '/' + f def doc_all_dirs(): """Collect the commands and paths to include in our docs.""" - scripts = [] - for root, _, files in os.walk('scripts'): - scripts.extend(doc_dir(root, files)) - return tuple(scripts) + tools = [] + # TODO: as we scan the docs, parse out the tags and short descriptions and + # build a map for use in generating the tags pages and links in the tool + # doc footers + for root, _, files in os.walk('docs/plugins'): + tools.extend(doc_dir(root, files, os.path.relpath(root, 'docs/plugins'))) + for root, _, files in os.walk('scripts/docs'): + tools.extend(doc_dir(root, files, os.path.relpath(root, 'scripts/docs'))) + return tuple(tools) DOC_ALL_DIRS = doc_all_dirs() +def generate_tag_indices(): + #TODO: generate docs/tags/.rst with the tag description and links to the + # tools that have that tag + os.makedirs('docs/tags', mode=0o755, exist_ok=True) -def document_scripts(): - """Autodoc for files with the magic script documentation marker strings. - Returns a dict of script-kinds to lists of .rst include directives. +def write_tool_docs(): """ - # Next we split by type and create include directives sorted by command - kinds = {'base': [], 'devel': [], 'fix': [], 'gui': [], 'modtools': []} - for s in DOC_ALL_DIRS: - k_fname = s[0].split('/', 1) - if len(k_fname) == 1: - kinds['base'].append(s) - else: - kinds[k_fname[0]].append(s) - - def template(arg): - tmp = '.. _{}:\n\n.. include:: /{}\n' +\ - ' :start-after: {}\n :end-before: {}\n' - if arg[0] in KEYBINDS: - tmp += '\n:dfhack-keybind:`{}`\n'.format(arg[0]) - return tmp.format(*arg) - - return {key: '\n\n'.join(map(template, sorted(value))) - for key, value in kinds.items()} - - -def write_script_docs(): + Creates a file for each tool with the ".. include::" directives to pull in + the original documentation. Then we generate a label and useful info in the + footer. """ - Creates a file for eack kind of script (base/devel/fix/gui/modtools) - with all the ".. include::" directives to pull out docs between the - magic strings. - """ - kinds = document_scripts() - head = { - 'base': 'Basic Scripts', - 'devel': 'Development Scripts', - 'fix': 'Bugfixing Scripts', - 'gui': 'GUI Scripts', - 'modtools': 'Scripts for Modders'} - for k in head: - title = ('.. _scripts-{k}:\n\n{l}\n{t}\n{l}\n\n' - '.. include:: /scripts/{a}about.txt\n\n' - '.. contents:: Contents\n' - ' :local:\n\n').format( - k=k, t=head[k], - l=len(head[k])*'#', - a=('' if k == 'base' else k + '/') - ) + for k in DOC_ALL_DIRS: + label = ('.. _{name}:\n\n').format(name=k[0]) + # TODO: can we autogenerate the :dfhack-keybind: line? it would go beneath + # the tool header, which is currently in the middle of the included file. + # should we remove those headers from the doc files and just generate them + # here? That might be easier. But then where will the tags go? It would + # look better if they were above the keybinds, but then we'd be in the + # same situation. + include = ('.. include:: /{path}\n\n').format(path=k[1]) + # TODO: generate a footer with links to tools that share at least one + # tag with this tool. Just the tool names, strung across the bottom of + # the page in one long wrapped line, similar to how the wiki does it mode = 'w' if sys.version_info.major > 2 else 'wb' - with open('docs/_auto/{}.rst'.format(k), mode) as outfile: - outfile.write(title) - outfile.write(kinds[k]) + os.makedirs(os.path.join('docs/tools', os.path.dirname(k[0])), + mode=0o755, exist_ok=True) + with open('docs/tools/{}.rst'.format(k[0]), mode) as outfile: + if k[0] != 'search': + outfile.write(label) + outfile.write(include) def all_keybinds_documented(): """Check that all keybindings are documented with the :dfhack-keybind: directive somewhere.""" - configured_binds = set(KEYBINDS) - script_commands = set(i[0] for i in DOC_ALL_DIRS) - with open('./docs/Plugins.rst') as f: - plugin_binds = set(re.findall(':dfhack-keybind:`(.*?)`', f.read())) - undocumented_binds = configured_binds - script_commands - plugin_binds + undocumented_binds = set(KEYBINDS) + tools = set(i[0] for i in DOC_ALL_DIRS) + for t in tools: + with open(('./docs/tools/{}.rst').format(t)) as f: + tool_binds = set(re.findall(':dfhack-keybind:`(.*?)`', f.read())) + undocumented_binds -= tool_binds if undocumented_binds: raise ValueError('The following DFHack commands have undocumented ' 'keybindings: {}'.format(sorted(undocumented_binds))) # Actually call the docs generator and run test -write_script_docs() -all_keybinds_documented() +write_tool_docs() +generate_tag_indices() +#all_keybinds_documented() # comment out while we're transitioning # -- General configuration ------------------------------------------------ @@ -203,6 +180,8 @@ extensions = [ 'dfhack.lexer', ] +sphinx_major_version = sphinx.version_info[0] + def get_caption_str(prefix=''): return prefix + (sphinx_major_version >= 5 and '%s' or '') @@ -282,11 +261,11 @@ today_fmt = html_last_updated_fmt = '%Y-%m-%d' # directories to ignore when looking for source files. exclude_patterns = [ 'README.md', - 'docs/html*', - 'depends/*', 'build*', - 'docs/_auto/news*', - 'docs/_changelogs/', + 'depends/*', + 'docs/html/*', + 'docs/text/*', + 'docs/plugins/*', 'scripts/docs/*', ] diff --git a/docs/Core.rst b/docs/Core.rst index ffa620f55..f7ba0824b 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -15,19 +15,18 @@ DFHack commands can be implemented in any of three ways: :builtin: commands are implemented by the core of DFHack. They manage other DFHack tools, interpret commands, and control basic - aspects of DF (force pause or quit). They are documented - `here `. + aspects of DF (force pause or quit). :plugins: are stored in ``hack/plugins/`` and must be compiled with the same version of DFHack. They are less flexible than scripts, but used for complex or ongoing tasks because they run faster. - Plugins included with DFHack are documented `here `. :scripts: are Ruby or Lua scripts stored in ``hack/scripts/``. Because they don't need to be compiled, scripts are more flexible about versions, and easier to distribute. - Most third-party DFHack addons are scripts. All scripts included - with DFHack are documented `here `. + Most third-party DFHack addons are scripts. + +All tools distributed with DFHack are documented `here `. Using DFHack Commands ===================== @@ -186,7 +185,7 @@ where ``*`` can be any string, including the empty string. A world being loaded can mean a fortress, an adventurer, or legends mode. These files are best used for non-persistent commands, such as setting -a `fix ` script to run on `repeat`. +a `fix ` script to run on `repeat`. .. _onMapLoad.init: diff --git a/docs/Dev-intro.rst b/docs/Dev-intro.rst index 758bf225f..6f46d86c5 100644 --- a/docs/Dev-intro.rst +++ b/docs/Dev-intro.rst @@ -41,7 +41,7 @@ Installed plugins live in the ``hack/plugins`` folder of a DFHack installation, and the `load` family of commands can be used to load a recompiled plugin without restarting DF. -See `plugins-index` for a list of all plugins included in DFHack. +Run `plug` at the DFHack prompt for a list of all plugins included in DFHack. Scripts ------- @@ -51,8 +51,9 @@ is more complete and currently better-documented, however. Referring to existing scripts as well as the API documentation can be helpful when developing new scripts. -`Scripts included in DFHack ` live in a separate `scripts repository `_. -This can be found in the ``scripts`` submodule if you have +`Scripts included in DFHack ` live in a separate +`scripts repository `_. This can be found in +the ``scripts`` submodule if you have `cloned DFHack `, or the ``hack/scripts`` folder of an installed copy of DFHack. diff --git a/docs/Documentation.rst b/docs/Documentation.rst index be4511ba8..acfffdbbb 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -318,10 +318,10 @@ changelogs are combined as part of the changelog build process: * ``scripts/changelog.txt`` for changes made to scripts in the ``scripts`` repo * ``library/xml/changelog.txt`` for changes made in the ``df-structures`` repo -Building the changelogs generates two files: ``docs/_auto/news.rst`` and -``docs/_auto/news-dev.rst``. These correspond to `changelog` and `dev-changelog` -and contain changes organized by stable and development DFHack releases, -respectively. For example, an entry listed under "0.44.05-alpha1" in +Building the changelogs generates two files: ``docs/changelogs/news.rst`` and +``docs/changelogs/news-dev.rst``. These correspond to `changelog` and +`dev-changelog` and contain changes organized by stable and development DFHack +releases, respectively. For example, an entry listed under "0.44.05-alpha1" in changelog.txt will be listed under that version in the development changelog as well, but under "0.44.05-r1" in the stable changelog (assuming that is the closest stable release after 0.44.05-alpha1). An entry listed under a stable diff --git a/docs/History.rst b/docs/History.rst index dc02d02b0..67b65f3bc 100644 --- a/docs/History.rst +++ b/docs/History.rst @@ -1350,7 +1350,7 @@ Misc improvements - `createitem`: in adventure mode it now defaults to the controlled unit as maker. - `autotrade`: adds "(Un)mark All" options to both panes of trade screen. - `mousequery`: several usability improvements; show live overlay (in menu area) of what's on the tile under the mouse cursor. -- `search`: workshop profile search added. +- `search-plugin`: workshop profile search added. - `dwarfmonitor`: add screen to summarise preferences of fortress dwarfs. - `getplants`: add autochop function to automate woodcutting. - `stocks`: added more filtering and display options. @@ -1510,7 +1510,7 @@ New binary patches New Plugins ----------- - `fix-armory`: Together with a couple of binary patches and the `gui/assign-rack` script, this plugin makes weapon racks, armor stands, chests and cabinets in properly designated barracks be used again for storage of squad equipment. -- `search`: Adds an incremental search function to the Stocks, Trading, Stockpile and Unit List screens. +- `search-plugin`: Adds an incremental search function to the Stocks, Trading, Stockpile and Unit List screens. - `automaterial`: Makes building constructions (walls, floors, fortifications, etc) a little bit easier by saving you from having to trawl through long lists of materials each time you place one. - Dfusion: Reworked to make use of lua modules, now all the scripts can be used from other scripts. - Eventful: A collection of lua events, that will allow new ways to interact with df world. diff --git a/docs/Introduction.rst b/docs/Introduction.rst index 27c887a8b..f0dc7a4b6 100644 --- a/docs/Introduction.rst +++ b/docs/Introduction.rst @@ -21,7 +21,7 @@ enhancements by default, and more can be enabled. There are also many tools You can even add third-party scripts and plugins to do almost anything! For modders, DFHack makes many things possible. Custom reactions, new -interactions, magic creature abilities, and more can be set through `scripts-modtools` +interactions, magic creature abilities, and more can be set through `DFHack tools ` and custom raws. Non-standard DFHack scripts and inits can be stored in the raw directory, making raws or saves fully self-contained for distribution - or for coexistence in a single DF install, even with incompatible components. diff --git a/docs/Memory-research.rst b/docs/Memory-research.rst index 9729072f5..bc7b45fd7 100644 --- a/docs/Memory-research.rst +++ b/docs/Memory-research.rst @@ -63,7 +63,7 @@ are not built by default, but can be built by setting the ``BUILD_DEVEL`` Scripts ~~~~~~~ -Several `development scripts ` can be useful for memory research. +Several `development scripts ` can be useful for memory research. These include (but are not limited to): - `devel/dump-offsets` diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index f0c47fe96..f77737176 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -17,4 +17,4 @@ See `changelog` for a list of changes grouped by stable releases. :local: :depth: 1 -.. include:: /docs/_auto/news-dev.rst +.. include:: /docs/changelogs/news-dev.rst diff --git a/docs/NEWS.rst b/docs/NEWS.rst index 1283b079a..fb33aebf9 100644 --- a/docs/NEWS.rst +++ b/docs/NEWS.rst @@ -17,7 +17,7 @@ See `dev-changelog` for a list of changes grouped by development releases. :local: :depth: 1 -.. include:: /docs/_auto/news.rst +.. include:: /docs/changelogs/news.rst Older Changelogs diff --git a/docs/Plugins.rst b/docs/Plugins.rst deleted file mode 100644 index 8acddfc6d..000000000 --- a/docs/Plugins.rst +++ /dev/null @@ -1,3436 +0,0 @@ -.. _plugins-index: - -############## -DFHack Plugins -############## - -DFHack plugins are the commands, that are compiled with a specific version. -They can provide anything from a small keybinding, to a complete overhaul of -game subsystems or the entire renderer. - -Most commands offered by plugins are listed here, -hopefully organised in a way you will find useful. - -.. contents:: Contents - :local: - :depth: 2 - -======== -Bugfixes -======== - -.. contents:: - :local: - -.. _fix-armory: - -fix-armory -========== -`This plugin requires a binpatch `, which has not -been available since DF 0.34.11 - -.. _fix-unit-occupancy: - -fix-unit-occupancy -================== -This plugin fixes issues with unit occupancy, notably phantom -"unit blocking tile" messages (:bug:`3499`). It can be run manually, or -periodically when enabled with the built-in enable/disable commands: - -:(no argument): Run the plugin once immediately, for the whole map. -:-h, here, cursor: Run immediately, only operate on the tile at the cursor -:-n, dry, dry-run: Run immediately, do not write changes to map -:interval : Run the plugin every ``X`` ticks (when enabled). - The default is 1200 ticks, or 1 day. - Ticks are only counted when the game is unpaused. - -.. _fixveins: - -fixveins -======== -Removes invalid references to mineral inclusions and restores missing ones. -Use this if you broke your embark with tools like `tiletypes`, or if you -accidentally placed a construction on top of a valuable mineral floor. - -.. _petcapRemover: - -petcapRemover -============= -Allows you to remove or raise the pet population cap. In vanilla -DF, pets will not reproduce unless the population is below 50 and the number of -children of that species is below a certain percentage. This plugin allows -removing the second restriction and removing or raising the first. Pets still -require PET or PET_EXOTIC tags in order to reproduce. Type ``help petcapRemover`` -for exact usage. In order to make population more stable and avoid sudden -population booms as you go below the raised population cap, this plugin counts -pregnancies toward the new population cap. It can still go over, but only in the -case of multiple births. - -Usage: - -:petcapRemover: cause pregnancies now and schedule the next check -:petcapRemover every n: set how often in ticks the plugin checks for possible pregnancies -:petcapRemover cap n: set the new cap to n. if n = 0, no cap -:petcapRemover pregtime n: sets the pregnancy duration to n ticks. natural pregnancies are - 300000 ticks for the current race and 200000 for everyone else - -.. _tweak: - -tweak -===== -Contains various tweaks for minor bugs. - -One-shot subcommands: - -:clear-missing: Remove the missing status from the selected unit. - This allows engraving slabs for ghostly, but not yet - found, creatures. -:clear-ghostly: Remove the ghostly status from the selected unit and mark - it as dead. This allows getting rid of bugged ghosts - which do not show up in the engraving slab menu at all, - even after using clear-missing. It works, but is - potentially very dangerous - so use with care. Probably - (almost certainly) it does not have the same effects like - a proper burial. You've been warned. -:fixmigrant: Remove the resident/merchant flag from the selected unit. - Intended to fix bugged migrants/traders who stay at the - map edge and don't enter your fort. Only works for - dwarves (or generally the player's race in modded games). - Do NOT abuse this for 'real' caravan merchants (if you - really want to kidnap them, use 'tweak makeown' instead, - otherwise they will have their clothes set to forbidden etc). -:makeown: Force selected unit to become a member of your fort. - Can be abused to grab caravan merchants and escorts, even if - they don't belong to the player's race. Foreign sentients - (humans, elves) can be put to work, but you can't assign rooms - to them and they don't show up in DwarfTherapist because the - game treats them like pets. Grabbing draft animals from - a caravan can result in weirdness (animals go insane or berserk - and are not flagged as tame), but you are allowed to mark them - for slaughter. Grabbing wagons results in some funny spam, then - they are scuttled. - -Subcommands that persist until disabled or DF quits: - -.. comment: sort these alphabetically - -:adamantine-cloth-wear: Prevents adamantine clothing from wearing out while being worn (:bug:`6481`). -:advmode-contained: Works around :bug:`6202`, custom reactions with container inputs - in advmode. The issue is that the screen tries to force you to select - the contents separately from the container. This forcefully skips child - reagents. -:block-labors: Prevents labors that can't be used from being toggled -:burrow-name-cancel: Implements the "back" option when renaming a burrow, - which currently does nothing (:bug:`1518`) -:cage-butcher: Adds an option to butcher units when viewing cages with :kbd:`q` -:civ-view-agreement: Fixes overlapping text on the "view agreement" screen -:condition-material: Fixes a crash in the work order contition material list (:bug:`9905`). -:craft-age-wear: Fixes the behavior of crafted items wearing out over time (:bug:`6003`). - With this tweak, items made from cloth and leather will gain a level of - wear every 20 years. -:do-job-now: Adds a job priority toggle to the jobs list -:embark-profile-name: Allows the use of lowercase letters when saving embark profiles -:eggs-fertile: Displays a fertility indicator on nestboxes -:farm-plot-select: Adds "Select all" and "Deselect all" options to farm plot menus -:fast-heat: Further improves temperature update performance by ensuring that 1 degree - of item temperature is crossed in no more than specified number of frames - when updating from the environment temperature. This reduces the time it - takes for stable-temp to stop updates again when equilibrium is disturbed. -:fast-trade: Makes Shift-Down in the Move Goods to Depot and Trade screens select - the current item (fully, in case of a stack), and scroll down one line. -:fps-min: Fixes the in-game minimum FPS setting -:hide-priority: Adds an option to hide designation priority indicators -:hotkey-clear: Adds an option to clear currently-bound hotkeys (in the :kbd:`H` menu) -:import-priority-category: - Allows changing the priority of all goods in a - category when discussing an import agreement with the liaison -:kitchen-prefs-all: Adds an option to toggle cook/brew for all visible items in kitchen preferences -:kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences -:kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs (:bug:`9000`) -:max-wheelbarrow: Allows assigning more than 3 wheelbarrows to a stockpile -:military-color-assigned: - Color squad candidates already assigned to other squads in yellow/green - to make them stand out more in the list. - - .. image:: images/tweak-mil-color.png - -:military-stable-assign: - Preserve list order and cursor position when assigning to squad, - i.e. stop the rightmost list of the Positions page of the military - screen from constantly resetting to the top. -:nestbox-color: Fixes the color of built nestboxes -:partial-items: Displays percentages on partially-consumed items such as hospital cloth -:reaction-gloves: Fixes reactions to produce gloves in sets with correct handedness (:bug:`6273`) -:shift-8-scroll: Gives Shift-8 (or :kbd:`*`) priority when scrolling menus, instead of scrolling the map -:stable-cursor: Saves the exact cursor position between t/q/k/d/b/etc menus of fortress mode, if the - map view is near enough to its previous position. -:stone-status-all: Adds an option to toggle the economic status of all stones -:title-start-rename: Adds a safe rename option to the title screen "Start Playing" menu -:tradereq-pet-gender: Displays pet genders on the trade request screen - -.. comment: sort these alphabetically - - -=============================== -Data inspection and visualizers -=============================== - -.. contents:: - :local: - -.. _blueprint: - -blueprint -========= -The ``blueprint`` command exports the structure of a portion of your fortress in -a blueprint file that you (or anyone else) can later play back with `quickfort`. - -Blueprints are ``.csv`` or ``.xlsx`` files created in the ``blueprints`` -subdirectory of your DF folder. The map area to turn into a blueprint is either -selected interactively with the ``blueprint gui`` command or, if the GUI is not -used, starts at the active cursor location and extends right and down for the -requested width and height. - -**Usage:** - - ``blueprint [] [ []] []`` - - ``blueprint gui [ []] []`` - -**Examples:** - -``blueprint gui`` - Runs `gui/blueprint`, the interactive frontend, where all configuration for - a ``blueprint`` command can be set visually and interactively. - -``blueprint 30 40 bedrooms`` - Generates blueprints for an area 30 tiles wide by 40 tiles tall, starting - from the active cursor on the current z-level. Blueprints are written - sequentially to ``bedrooms.csv`` in the ``blueprints`` directory. - -``blueprint 30 40 bedrooms dig --cursor 108,100,150`` - Generates only the ``#dig`` blueprint in the ``bedrooms.csv`` file, and - the start of the blueprint area is set to a specific value instead of using - the in-game cursor position. - -**Positional Parameters:** - -:``width``: Width of the area (in tiles) to translate. -:``height``: Height of the area (in tiles) to translate. -:``depth``: Number of z-levels to translate. Positive numbers go *up* from the - cursor and negative numbers go *down*. Defaults to 1 if not specified, - indicating that the blueprint should only include the current z-level. -:``name``: Base name for blueprint files created in the ``blueprints`` - directory. If no name is specified, "blueprint" is used by default. The - string must contain some characters other than numbers so the name won't be - confused with the optional ``depth`` parameter. - -**Phases:** - -If you want to generate blueprints only for specific phases, add their names to -the commandline, anywhere after the blueprint base name. You can list multiple -phases; just separate them with a space. - -:``dig``: Generate quickfort ``#dig`` blueprints for digging natural stone. -:``carve``: Generate quickfort ``#dig`` blueprints for smoothing and carving. -:``build``: Generate quickfort ``#build`` blueprints for constructions and - buildings. -:``place``: Generate quickfort ``#place`` blueprints for placing stockpiles. -:``zone``: Generate quickfort ``#zone`` blueprints for designating zones. -:``query``: Generate quickfort ``#query`` blueprints for configuring rooms. - -If no phases are specified, phases are autodetected. For example, a ``#place`` -blueprint will be created only if there are stockpiles in the blueprint area. - -**Options:** - -``-c``, ``--cursor ,,``: - Use the specified map coordinates instead of the current cursor position for - the upper left corner of the blueprint range. If this option is specified, - then an active game map cursor is not necessary. -``-e``, ``--engrave``: - Record engravings in the ``carve`` phase. If this option is not specified, - engravings are ignored. -``-f``, ``--format ``: - Select the output format of the generated files. See the ``Output formats`` - section below for options. If not specified, the output format defaults to - "minimal", which will produce a small, fast ``.csv`` file. -``-h``, ``--help``: - Show command help text. -``-s``, ``--playback-start ,,``: - Specify the column and row offsets (relative to the upper-left corner of the - blueprint, which is ``1,1``) where the player should put the cursor when the - blueprint is played back with `quickfort`, in - `quickfort start marker ` format, for example: - ``10,10,central stairs``. If there is a space in the comment, you will need - to surround the parameter string in double quotes: ``"-s10,10,central stairs"`` or - ``--playback-start "10,10,central stairs"`` or - ``"--playback-start=10,10,central stairs"``. -``-t``, ``--splitby ``: - Split blueprints into multiple files. See the ``Splitting output into - multiple files`` section below for details. If not specified, defaults to - "none", which will create a standard quickfort - `multi-blueprint ` file. - -**Output formats:** - -Here are the values that can be passed to the ``--format`` flag: - -:``minimal``: - Creates ``.csv`` files with minimal file size that are fast to read and - write. This is the default. -:``pretty``: - Makes the blueprints in the ``.csv`` files easier to read and edit with a text - editor by adding extra spacing and alignment markers. - -**Splitting output into multiple files:** - -The ``--splitby`` flag can take any of the following values: - -:``none``: - Writes all blueprints into a single file. This is the standard format for - quickfort fortress blueprint bundles and is the default. -:``phase``: - Creates a separate file for each phase. - -.. _cursecheck: - -cursecheck -========== -Checks a single map tile or the whole map/world for cursed creatures (ghosts, -vampires, necromancers, werebeasts, zombies). - -With an active in-game cursor only the selected tile will be observed. -Without a cursor the whole map will be checked. - -By default cursed creatures will be only counted in case you just want to find -out if you have any of them running around in your fort. Dead and passive -creatures (ghosts who were put to rest, killed vampires, ...) are ignored. -Undead skeletons, corpses, bodyparts and the like are all thrown into the curse -category "zombie". Anonymous zombies and resurrected body parts will show -as "unnamed creature". - -Options: - -:detail: Print full name, date of birth, date of curse and some status - info (some vampires might use fake identities in-game, though). -:ids: Print the creature and race IDs. -:nick: Set the type of curse as nickname (does not always show up - in-game, some vamps don't like nicknames). -:all: Include dead and passive cursed creatures (can result in a quite - long list after having FUN with necromancers). -:verbose: Print all curse tags (if you really want to know it all). - -Examples: - -``cursecheck detail all`` - Give detailed info about all cursed creatures including deceased ones (no - in-game cursor). -``cursecheck nick`` - Give a nickname all living/active cursed creatures on the map(no in-game - cursor). - -.. note:: - - If you do a full search (with the option "all") former ghosts will show up - with the cursetype "unknown" because their ghostly flag is not set. - - Please report any living/active creatures with cursetype "unknown" - - this is most likely with mods which introduce new types of curses. - -.. _flows: - -flows -===== -A tool for checking how many tiles contain flowing liquids. If you suspect that -your magma sea leaks into HFS, you can use this tool to be sure without -revealing the map. - -.. _isoworldremote: - -isoworldremote -============== -A plugin that implements a `remote API ` used by Isoworld. - -.. _probe: - -probe -===== - -This plugin provides multiple commands that print low-level properties of the -selected objects. - -* ``probe``: prints some properties of the tile selected with :kbd:`k`. Some of - these properties can be passed into `tiletypes`. -* ``cprobe``: prints some properties of the unit selected with :kbd:`v`, as well - as the IDs of any worn items. `gui/gm-unit` and `gui/gm-editor` are more - complete in-game alternatives. -* ``bprobe``: prints some properties of the building selected with :kbd:`q` or - :kbd:`t`. `gui/gm-editor` is a more complete in-game alternative. - -.. _prospect: -.. _prospector: - -prospect -======== - -**Usage:** - - ``prospect [all|hell] []`` - -Shows a summary of resources that exist on the map. By default, only the visible -part of the map is scanned. Include the ``all`` keyword if you want ``prospect`` -to scan the whole map as if it were revealed. Use ``hell`` instead of ``all`` if -you also want to see the Z range of HFS tubes in the 'features' report section. - -**Options:** - -:``-h``, ``--help``: - Shows this help text. -:``-s``, ``--show ``: - Shows only the named comma-separated list of report sections. Report section - names are: summary, liquids, layers, features, ores, gems, veins, shrubs, - and trees. If run during pre-embark, only the layers, ores, gems, and veins - report sections are available. -:``-v``, ``--values``: - Includes material value in the output. Most useful for the 'gems' report - section. - -**Examples:** - -``prospect all`` - Shows the entire report for the entire map. - -``prospect hell --show layers,ores,veins`` - Shows only the layers, ores, and other vein stone report sections, and - includes information on HFS tubes when a fort is loaded. - -``prospect all -sores`` - Show only information about ores for the pre-embark or fortress map report. - -**Pre-embark estimate:** - -If prospect is called during the embark selection screen, it displays an -estimate of layer stone availability. If the ``all`` keyword is specified, it -also estimates ores, gems, and vein material. The estimate covers all tiles of -the embark rectangle. - -.. note:: - - The results of pre-embark prospect are an *estimate*, and can at best be - expected to be somewhere within +/- 30% of the true amount; sometimes it - does a lot worse. Especially, it is not clear how to precisely compute how - many soil layers there will be in a given embark tile, so it can report a - whole extra layer, or omit one that is actually present. - -.. _remotefortressreader: - -remotefortressreader -==================== -An in-development plugin for realtime fortress visualisation. -See :forums:`Armok Vision <146473>`. - -.. _reveal: -.. _unreveal: -.. _revtoggle: -.. _revflood: -.. _revforget: - -reveal -====== -This reveals the map. By default, HFS will remain hidden so that the demons -don't spawn. You can use ``reveal hell`` to reveal everything. With hell revealed, -you won't be able to unpause until you hide the map again. If you really want -to unpause with hell revealed, use ``reveal demons``. - -Reveal also works in adventure mode, but any of its effects are negated once -you move. When you use it this way, you don't need to run ``unreveal``. - -Usage and related commands: - -:reveal: Reveal the whole map, except for HFS to avoid demons spawning -:reveal hell: Also show hell, but requires ``unreveal`` before unpausing -:reveal demon: Reveals everything and allows unpausing - good luck! -:unreveal: Reverts the effects of ``reveal`` -:revtoggle: Switches between ``reveal`` and ``unreveal`` -:revflood: Hide everything, then reveal tiles with a path to the cursor. - Note that tiles behind constructed walls are also revealed as a - workaround for :bug:`1871`. -:revforget: Discard info about what was visible before revealing the map. - Only useful where (e.g.) you abandoned with the fort revealed - and no longer want the data. - -.. _showmood: - -showmood -======== -Shows all items needed for the currently active strange mood. - -.. _spectate: - -spectate -======== -Simple plugin to automate following random dwarves. Most of the time things will -be weighted towards z-levels with the highest job activity. Simply enter the -``spectate`` command to toggle the plugin's state. - -.. _plugin-stonesense: - -stonesense -========== -An isometric visualizer that runs in a second window. Usage: - -:stonesense: Open the visualiser in a new window. Alias ``ssense``. -:ssense overlay: Overlay DF window, replacing the map area. - -For more information, see `the full Stonesense README `. - -=========================== -Job and Fortress management -=========================== - -.. contents:: - :local: - - -.. _autobutcher: - -autobutcher -=========== -Assigns lifestock for slaughter once it reaches a specific count. Requires that -you add the target race(s) to a watch list. Only tame units will be processed. - -Units will be ignored if they are: - -* Nicknamed (for custom protection; you can use the `rename` ``unit`` tool - individually, or `zone` ``nick`` for groups) -* Caged, if and only if the cage is defined as a room (to protect zoos) -* Trained for war or hunting - -Creatures who will not reproduce (because they're not interested in the -opposite sex or have been gelded) will be butchered before those who will. -Older adults and younger children will be butchered first if the population -is above the target (default 1 male, 5 female kids and adults). Note that -you may need to set a target above 1 to have a reliable breeding population -due to asexuality etc. See `fix-ster` if this is a problem. - -Options: - -:example: Print some usage examples. -:start: Start running every X frames (df simulation ticks). - Default: X=6000, which would be every 60 seconds at 100fps. -:stop: Stop running automatically. -:sleep : Changes the timer to sleep X frames between runs. -:watch R: Start watching a race. R can be a valid race RAW id (ALPACA, - BIRD_TURKEY, etc) or a list of ids seperated by spaces or - the keyword 'all' which affects all races on your current - watchlist. -:unwatch R: Stop watching race(s). The current target settings will be - remembered. R can be a list of ids or the keyword 'all'. -:forget R: Stop watching race(s) and forget it's/their target settings. - R can be a list of ids or the keyword 'all'. -:autowatch: Automatically adds all new races (animals you buy from merchants, - tame yourself or get from migrants) to the watch list using - default target count. -:noautowatch: Stop auto-adding new races to the watchlist. -:list: Print the current status and watchlist. -:list_export: Print the commands needed to set up status and watchlist, - which can be used to import them to another save (see notes). -:target : - Set target count for specified race(s). The first four arguments - are the number of female and male kids, and female and male adults. - R can be a list of spceies ids, or the keyword ``all`` or ``new``. - ``R = 'all'``: change target count for all races on watchlist - and set the new default for the future. ``R = 'new'``: don't touch - current settings on the watchlist, only set the new default - for future entries. -:list_export: Print the commands required to rebuild your current settings. - -.. note:: - - Settings and watchlist are stored in the savegame, so that you can have - different settings for each save. If you want to copy your watchlist to - another savegame you must export the commands required to recreate your settings. - - To export, open an external terminal in the DF directory, and run - ``dfhack-run autobutcher list_export > filename.txt``. To import, load your - new save and run ``script filename.txt`` in the DFHack terminal. - - -Examples: - -You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, -1 male) of the race alpaca. Once the kids grow up the oldest adults will get -slaughtered. Excess kids will get slaughtered starting with the youngest -to allow that the older ones grow into adults. Any unnamed cats will -be slaughtered as soon as possible. :: - - autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY - autobutcher target 0 0 0 0 CAT - autobutcher watch ALPACA BIRD_TURKEY CAT - autobutcher start - -Automatically put all new races onto the watchlist and mark unnamed tame units -for slaughter as soon as they arrive in your fort. Settings already made -for specific races will be left untouched. :: - - autobutcher target 0 0 0 0 new - autobutcher autowatch - autobutcher start - -Stop watching the races alpaca and cat, but remember the target count -settings so that you can use 'unwatch' without the need to enter the -values again. Note: 'autobutcher unwatch all' works, but only makes sense -if you want to keep the plugin running with the 'autowatch' feature or manually -add some new races with 'watch'. If you simply want to stop it completely use -'autobutcher stop' instead. :: - - autobutcher unwatch ALPACA CAT - -.. _autochop: - -autochop -======== -Automatically manage tree cutting designation to keep available logs withing given -quotas. - -Open the dashboard by running:: - - enable autochop - -The plugin must be activated (with :kbd:`d`-:kbd:`t`-:kbd:`c`-:kbd:`a`) before -it can be used. You can then set logging quotas and restrict designations to -specific burrows (with 'Enter') if desired. The plugin's activity cycle runs -once every in game day. - -If you add ``enable autochop`` to your dfhack.init there will be a hotkey to -open the dashboard from the chop designation menu. - -.. _autoclothing: - -autoclothing -============ - -Automatically manage clothing work orders, allowing the user to set how many of -each clothing type every citizen should have. Usage:: - - autoclothing [number] - -Examples: - -* ``autoclothing cloth "short skirt" 10``: - Sets the desired number of cloth short skirts available per citizen to 10. -* ``autoclothing cloth dress``: - Displays the currently set number of cloth dresses chosen per citizen. - -.. _autodump: - -autodump -======== -This plugin adds an option to the :kbd:`q` menu for stckpiles when `enabled `. -When autodump is enabled for a stockpile, any items placed in the stockpile will -automatically be designated to be dumped. - -Alternatively, you can use it to quickly move all items designated to be dumped. -Items are instantly moved to the cursor position, the dump flag is unset, -and the forbid flag is set, as if it had been dumped normally. -Be aware that any active dump item tasks still point at the item. - -Cursor must be placed on a floor tile so the items can be dumped there. - -Options: - -:destroy: Destroy instead of dumping. Doesn't require a cursor. - If called again before the game is resumed, cancels destroy. -:destroy-here: As ``destroy``, but only the selected item in the :kbd:`k` list, - or inside a container. - Alias ``autodump-destroy-here``, for keybindings. - :dfhack-keybind:`autodump-destroy-here` -:visible: Only process items that are not hidden. -:hidden: Only process hidden items. -:forbidden: Only process forbidden items (default: only unforbidden). - -``autodump-destroy-item`` destroys the selected item, which may be selected -in the :kbd:`k` list, or inside a container. If called again before the game -is resumed, cancels destruction of the item. -:dfhack-keybind:`autodump-destroy-item` - -.. _autofarm: - -autofarm -======== - -Automatically handles crop selection in farm plots based on current plant -stocks, and selects crops for planting if current stock is below a threshold. -Selected crops are dispatched on all farmplots. (Note that this plugin replaces -an older Ruby script of the same name.) - -Use the `enable` or `disable ` commands to change whether this plugin is -enabled. - -Usage: - -* ``autofarm runonce``: - Updates all farm plots once, without enabling the plugin -* ``autofarm status``: - Prints status information, including any applied limits -* ``autofarm default 30``: - Sets the default threshold -* ``autofarm threshold 150 helmet_plump tail_pig``: - Sets thresholds of individual plants - -.. _autogems: - -autogems -======== -Creates a new Workshop Order setting, automatically cutting rough gems -when `enabled `. - -See `gui/autogems` for a configuration UI. If necessary, the ``autogems-reload`` -command reloads the configuration file produced by that script. - -.. _autohauler: - -autohauler -========== -Autohauler is an autolabor fork. - -Rather than the all-of-the-above means of autolabor, autohauler will instead -only manage hauling labors and leave skilled labors entirely to the user, who -will probably use Dwarf Therapist to do so. - -Idle dwarves will be assigned the hauling labors; everyone else (including -those currently hauling) will have the hauling labors removed. This is to -encourage every dwarf to do their assigned skilled labors whenever possible, -but resort to hauling when those jobs are not available. This also implies -that the user will have a very tight skill assignment, with most skilled -labors only being assigned to just one dwarf, no dwarf having more than two -active skilled labors, and almost every non-military dwarf having at least -one skilled labor assigned. - -Autohauler allows skills to be flagged as to prevent hauling labors from -being assigned when the skill is present. By default this is the unused -ALCHEMIST labor but can be changed by the user. - -.. _autolabor: - -autolabor -========= -Automatically manage dwarf labors to efficiently complete jobs. -Autolabor tries to keep as many dwarves as possible busy but -also tries to have dwarves specialize in specific skills. - -The key is that, for almost all labors, once a dwarf begins a job it will finish that -job even if the associated labor is removed. Autolabor therefore frequently checks -which dwarf or dwarves should take new jobs for that labor, and sets labors accordingly. -Labors with equipment (mining, hunting, and woodcutting), which are abandoned -if labors change mid-job, are handled slightly differently to minimise churn. - -.. warning:: - - *autolabor will override any manual changes you make to labors while - it is enabled, including through other tools such as Dwarf Therapist* - -Simple usage: - -:enable autolabor: Enables the plugin with default settings. (Persistent per fortress) -:disable autolabor: Disables the plugin. - -Anything beyond this is optional - autolabor works well on the default settings. - -By default, each labor is assigned to between 1 and 200 dwarves (2-200 for mining). -By default 33% of the workforce become haulers, who handle all hauling jobs as well -as cleaning, pulling levers, recovering wounded, removing constructions, and filling ponds. -Other jobs are automatically assigned as described above. Each of these settings can be adjusted. - -Jobs are rarely assigned to nobles with responsibilities for meeting diplomats or merchants, -never to the chief medical dwarf, and less often to the bookeeper and manager. - -Hunting is never assigned without a butchery, and fishing is never assigned without a fishery. - -For each labor a preference order is calculated based on skill, biased against masters of other -trades and excluding those who can't do the job. The labor is then added to the best -dwarves for that labor. We assign at least the minimum number of dwarfs, in order of preference, -and then assign additional dwarfs that meet any of these conditions: - -* The dwarf is idle and there are no idle dwarves assigned to this labor -* The dwarf has non-zero skill associated with the labor -* The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled. - -We stop assigning dwarfs when we reach the maximum allowed. - -Advanced usage: - -:autolabor []: - Set number of dwarves assigned to a labor. -:autolabor haulers: Set a labor to be handled by hauler dwarves. -:autolabor disable: Turn off autolabor for a specific labor. -:autolabor reset: Return a labor to the default handling. -:autolabor reset-all: Return all labors to the default handling. -:autolabor list: List current status of all labors. -:autolabor status: Show basic status information. - -See `autolabor-artisans` for a differently-tuned setup. - -Examples: - -``autolabor MINE`` - Keep at least 5 dwarves with mining enabled. -``autolabor CUT_GEM 1 1`` - Keep exactly 1 dwarf with gemcutting enabled. -``autolabor COOK 1 1 3`` - Keep 1 dwarf with cooking enabled, selected only from the top 3. -``autolabor FEED_WATER_CIVILIANS haulers`` - Have haulers feed and water wounded dwarves. -``autolabor CUTWOOD disable`` - Turn off autolabor for wood cutting. - -.. _autonestbox: - -autonestbox -=========== -Assigns unpastured female egg-layers to nestbox zones. Requires that you create -pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox -must be in the top left corner. Only 1 unit will be assigned per pen, regardless -of the size. The age of the units is currently not checked, most birds grow up -quite fast. Egglayers who are also grazers will be ignored, since confining them -to a 1x1 pasture is not a good idea. Only tame and domesticated own units are -processed since pasturing half-trained wild egglayers could destroy your neat -nestbox zones when they revert to wild. When called without options autonestbox -will instantly run once. - -Options: - -:start: Start running every X frames (df simulation ticks). - Default: X=6000, which would be every 60 seconds at 100fps. -:stop: Stop running automatically. -:sleep: Must be followed by number X. Changes the timer to sleep X - frames between runs. - -.. _clean: - -clean -===== -Cleans all the splatter that get scattered all over the map, items and -creatures. In an old fortress, this can significantly reduce FPS lag. It can -also spoil your !!FUN!!, so think before you use it. - -Options: - -:map: Clean the map tiles. By default, it leaves mud and snow alone. -:units: Clean the creatures. Will also clean hostiles. -:items: Clean all the items. Even a poisoned blade. - -Extra options for ``map``: - -:mud: Remove mud in addition to the normal stuff. -:snow: Also remove snow coverings. - -.. _cleanowned: - -cleanowned -========== -Confiscates items owned by dwarfs. By default, owned food on the floor -and rotten items are confistacted and dumped. - -Options: - -:all: confiscate all owned items -:scattered: confiscated and dump all items scattered on the floor -:x: confiscate/dump items with wear level 'x' and more -:X: confiscate/dump items with wear level 'X' and more -:dryrun: a dry run. combine with other options to see what will happen - without it actually happening. - -Example: - -``cleanowned scattered X`` - This will confiscate rotten and dropped food, garbage on the floors and any - worn items with 'X' damage and above. - -.. _dwarfmonitor: - -dwarfmonitor -============ -Records dwarf activity to measure fort efficiency. - -Options: - -:enable : Start monitoring ``mode``. ``mode`` can be "work", "misery", - "weather", or "all". This will enable all corresponding widgets, - if applicable. -:disable : Stop monitoring ``mode``, and disable corresponding widgets, if applicable. -:stats: Show statistics summary -:prefs: Show dwarf preferences summary -:reload: Reload configuration file (``dfhack-config/dwarfmonitor.json``) - -:dfhack-keybind:`dwarfmonitor` - -Widget configuration: - -The following types of widgets (defined in :file:`hack/lua/plugins/dwarfmonitor.lua`) -can be displayed on the main fortress mode screen: - -:date: Show the in-game date -:misery: Show overall happiness levels of all dwarves -:weather: Show current weather (rain/snow) -:cursor: Show the current mouse cursor position - -The file :file:`dfhack-config/dwarfmonitor.json` can be edited to control the -positions and settings of all widgets displayed. This file should contain a -JSON object with the key ``widgets`` containing an array of objects - see the -included file in the ``dfhack-config`` folder for an example: - -.. code-block:: lua - - { - "widgets": [ - { - "type": "widget type (weather, misery, etc.)", - "x": X coordinate, - "y": Y coordinate - <...additional options...> - } - ] - } - -X and Y coordinates begin at zero (in the upper left corner of the screen). -Negative coordinates will be treated as distances from the lower right corner, -beginning at 1 - e.g. an x coordinate of 0 is the leftmost column, while an x -coordinate of 1 is the rightmost column. - -By default, the x and y coordinates given correspond to the leftmost tile of -the widget. Including an ``anchor`` option set to ``right`` will cause the -rightmost tile of the widget to be located at this position instead. - -Some widgets support additional options: - -* ``date`` widget: - - * ``format``: specifies the format of the date. The following characters - are replaced (all others, such as punctuation, are not modified) - - * ``Y`` or ``y``: The current year - * ``M``: The current month, zero-padded if necessary - * ``m``: The current month, *not* zero-padded - * ``D``: The current day, zero-padded if necessary - * ``d``: The current day, *not* zero-padded - - The default date format is ``Y-M-D``, per the ISO8601_ standard. - - .. _ISO8601: https://en.wikipedia.org/wiki/ISO_8601 - -* ``cursor`` widget: - - * ``format``: Specifies the format. ``X``, ``x``, ``Y``, and ``y`` are - replaced with the corresponding cursor cordinates, while all other - characters are unmodified. - * ``show_invalid``: If set to ``true``, the mouse coordinates will both be - displayed as ``-1`` when the cursor is outside of the DF window; otherwise, - nothing will be displayed. - -.. _dwarfvet: - -dwarfvet -======== -Enables Animal Caretaker functionality - -Always annoyed your dragons become useless after a minor injury? Well, with -dwarfvet, your animals become first rate members of your fort. It can also -be used to train medical skills. - -Animals need to be treated in an animal hospital, which is simply a hospital -that is also an animal training zone. The console will print out a list on game -load, and whenever one is added or removed. Dwarfs must have the Animal Caretaker -labor to treat animals. Normal medical skills are used (and no experience is given -to the Animal Caretaker skill). - -Options: - -:enable: Enables Animal Caretakers to treat and manage animals -:disable: Turns off the plguin -:report: Reports all zones that the game considers animal hospitals - -.. _fix-job-postings: - -fix-job-postings ----------------- -This command fixes crashes caused by previous versions of workflow, mostly in -DFHack 0.40.24-r4, and should be run automatically when loading a world (but can -also be run manually if desired). - -.. _job: - -job -=== -Command for general job query and manipulation. - -Options: - -*no extra options* - Print details of the current job. The job can be selected - in a workshop, or the unit/jobs screen. -**list** - Print details of all jobs in the selected workshop. -**item-material ** - Replace the exact material id in the job item. -**item-type ** - Replace the exact item type id in the job item. - -.. _job-duplicate: - -job-duplicate -============= -In :kbd:`q` mode, when a job is highlighted within a workshop or furnace -building, calling ``job-duplicate`` instantly duplicates the job. - -:dfhack-keybind:`job-duplicate` - -.. _job-material: - -job-material -============ -Alter the material of the selected job. Similar to ``job item-material ...`` - -Invoked as:: - - job-material - -:dfhack-keybind:`job-material` - -* In :kbd:`q` mode, when a job is highlighted within a workshop or furnace, - changes the material of the job. Only inorganic materials can be used - in this mode. -* In :kbd:`b` mode, during selection of building components positions the cursor - over the first available choice with the matching material. - -.. _labormanager: - -labormanager -============ -Automatically manage dwarf labors to efficiently complete jobs. -Labormanager is derived from autolabor (above) but uses a completely -different approach to assigning jobs to dwarves. While autolabor tries -to keep as many dwarves busy as possible, labormanager instead strives -to get jobs done as quickly as possible. - -Labormanager frequently scans the current job list, current list of -dwarfs, and the map to determine how many dwarves need to be assigned to -what labors in order to meet all current labor needs without starving -any particular type of job. - -.. warning:: - - *As with autolabor, labormanager will override any manual changes you - make to labors while it is enabled, including through other tools such - as Dwarf Therapist* - -Simple usage: - -:enable labormanager: Enables the plugin with default settings. - (Persistent per fortress) - -:disable labormanager: Disables the plugin. - -Anything beyond this is optional - labormanager works fairly well on the -default settings. - -The default priorities for each labor vary (some labors are higher -priority by default than others). The way the plugin works is that, once -it determines how many of each labor is needed, it then sorts them by -adjusted priority. (Labors other than hauling have a bias added to them -based on how long it's been since they were last used, to prevent job -starvation.) The labor with the highest priority is selected, the "best -fit" dwarf for that labor is assigned to that labor, and then its -priority is *halved*. This process is repeated until either dwarfs or -labors run out. - -Because there is no easy way to detect how many haulers are actually -needed at any moment, the plugin always ensures that at least one dwarf -is assigned to each of the hauling labors, even if no hauling jobs are -detected. At least one dwarf is always assigned to construction removing -and cleaning because these jobs also cannot be easily detected. Lever -pulling is always assigned to everyone. Any dwarfs for which there are -no jobs will be assigned hauling, lever pulling, and cleaning labors. If -you use animal trainers, note that labormanager will misbehave if you -assign specific trainers to specific animals; results are only guaranteed -if you use "any trainer", and animal trainers will probably be -overallocated in any case. - -Labormanager also sometimes assigns extra labors to currently busy -dwarfs so that when they finish their current job, they will go off and -do something useful instead of standing around waiting for a job. - -There is special handling to ensure that at least one dwarf is assigned -to haul food whenever food is detected left in a place where it will rot -if not stored. This will cause a dwarf to go idle if you have no -storepiles to haul food to. - -Dwarfs who are unable to work (child, in the military, wounded, -handless, asleep, in a meeting) are entirely excluded from labor -assignment. Any dwarf explicitly assigned to a burrow will also be -completely ignored by labormanager. - -The fitness algorithm for assigning jobs to dwarfs generally attempts to -favor dwarfs who are more skilled over those who are less skilled. It -also tries to avoid assigning female dwarfs with children to jobs that -are "outside", favors assigning "outside" jobs to dwarfs who are -carrying a tool that could be used as a weapon, and tries to minimize -how often dwarfs have to reequip. - -Labormanager automatically determines medical needs and reserves health -care providers as needed. Note that this may cause idling if you have -injured dwarfs but no or inadequate hospital facilities. - -Hunting is never assigned without a butchery, and fishing is never -assigned without a fishery, and neither of these labors is assigned -unless specifically enabled. - -The method by which labormanager determines what labor is needed for a -particular job is complicated and, in places, incomplete. In some -situations, labormanager will detect that it cannot determine what labor -is required. It will, by default, pause and print an error message on -the dfhack console, followed by the message "LABORMANAGER: Game paused -so you can investigate the above message.". If this happens, please open -an issue on github, reporting the lines that immediately preceded this -message. You can tell labormanager to ignore this error and carry on by -typing ``labormanager pause-on-error no``, but be warned that some job may go -undone in this situation. - -Advanced usage: - -:labormanager enable: Turn plugin on. -:labormanager disable: Turn plugin off. -:labormanager priority : Set the priority value (see above) for labor to . -:labormanager reset : Reset the priority value of labor to its default. -:labormanager reset-all: Reset all priority values to their defaults. -:labormanager allow-fishing: Allow dwarfs to fish. *Warning* This tends to result in most of the fort going fishing. -:labormanager forbid-fishing: Forbid dwarfs from fishing. Default behavior. -:labormanager allow-hunting: Allow dwarfs to hunt. *Warning* This tends to result in as many dwarfs going hunting as you have crossbows. -:labormanager forbid-hunting: Forbid dwarfs from hunting. Default behavior. -:labormanager list: Show current priorities and current allocation stats. -:labormanager pause-on-error yes: Make labormanager pause if the labor inference engine fails. See above. -:labormanager pause-on-error no: Allow labormanager to continue past a labor inference engine failure. - -.. _nestboxes: - -nestboxes -========= - -Automatically scan for and forbid fertile eggs incubating in a nestbox. -Toggle status with `enable` or `disable `. - -.. _orders: - -orders -====== - -A plugin for manipulating manager orders. - -Subcommands: - -:list: Shows the list of previously exported orders, including the orders library. -:export NAME: Exports the current list of manager orders to a file named ``dfhack-config/orders/NAME.json``. -:import NAME: Imports manager orders from a file named ``dfhack-config/orders/NAME.json``. -:clear: Deletes all manager orders in the current embark. -:sort: Sorts current manager orders by repeat frequency so daily orders don't - prevent other orders from ever being completed: one-time orders first, then - yearly, seasonally, monthly, then finally daily. - -You can keep your orders automatically sorted by adding the following command to -your ``onMapLoad.init`` file:: - - repeat -name orders-sort -time 1 -timeUnits days -command [ orders sort ] - - -The orders library ------------------- - -DFHack comes with a library of useful manager orders that are ready for import: - -:source:`basic.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection of orders handles basic fort necessities: - -- prepared meals and food products (and by-products like oil) -- booze/mead -- thread/cloth/dye -- pots/jugs/buckets/mugs -- bags of leather, cloth, silk, and yarn -- crafts and totems from otherwise unusable by-products -- mechanisms/cages -- splints/crutches -- lye/soap -- ash/potash -- beds/wheelbarrows/minecarts -- scrolls - -You should import it as soon as you have enough dwarves to perform the tasks. -Right after the first migration wave is usually a good time. - -:source:`furnace.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection creates basic items that require heat. It is separated out from -``basic.json`` to give players the opportunity to set up magma furnaces first in -order to save resources. It handles: - -- charcoal (including smelting of bituminous coal and lignite) -- pearlash -- sand -- green/clear/crystal glass -- adamantine processing -- item melting - -Orders are missing for plaster powder until DF :bug:`11803` is fixed. - -:source:`military.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection adds high-volume smelting jobs for military-grade metal ores and -produces weapons and armor: - -- leather backpacks/waterskins/cloaks/quivers/armor -- bone/wooden bolts -- smelting for platinum, silver, steel, bronze, bismuth bronze, and copper (and - their dependencies) -- bronze/bismuth bronze/copper bolts -- platinum/silver/steel/iron/bismuth bronze/bronze/copper weapons and armor, - with checks to ensure only the best available materials are being used - -If you set a stockpile to take weapons and armor of less than masterwork quality -and turn on `automelt` (like what `dreamfort` provides on its industry level), -these orders will automatically upgrade your military equipment to masterwork. -Make sure you have a lot of fuel (or magma forges and furnaces) before you turn -``automelt`` on, though! - -This file should only be imported, of course, if you need to equip a military. - -:source:`smelting.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection adds smelting jobs for all ores. It includes handling the ores -already managed by ``military.json``, but has lower limits. This ensures all -ores will be covered if a player imports ``smelting`` but not ``military``, but -the higher-volume ``military`` orders will take priority if both are imported. - -:source:`rockstock.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection of orders keeps a small stock of all types of rock furniture. -This allows you to do ad-hoc furnishings of guildhalls, libraries, temples, or -other rooms with `buildingplan` and your masons will make sure there is always -stock on hand to fulfill the plans. - -:source:`glassstock.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Similar to ``rockstock`` above, this collection keeps a small stock of all types -of glass furniture. If you have a functioning glass industry, this is more -sustainable than ``rockstock`` since you can never run out of sand. If you have -plenty of rock and just want the variety, you can import both ``rockstock`` and -``glassstock`` to get a mixture of rock and glass furnishings in your fort. - -There are a few items that ``glassstock`` produces that ``rockstock`` does not, -since there are some items that can not be made out of rock, for example: - -- tubes and corkscrews for building magma-safe screw pumps -- windows -- terrariums (as an alternative to wooden cages) - -.. _seedwatch: - -seedwatch -========= -Watches the numbers of seeds available and enables/disables seed and plant cooking. - -Each plant type can be assigned a limit. If their number falls below that limit, -the plants and seeds of that type will be excluded from cookery. -If the number rises above the limit + 20, then cooking will be allowed. - -The plugin needs a fortress to be loaded and will deactivate automatically otherwise. -You have to reactivate with 'seedwatch start' after you load the game. - -Options: - -:all: Adds all plants from the abbreviation list to the watch list. -:start: Start watching. -:stop: Stop watching. -:info: Display whether seedwatch is watching, and the watch list. -:clear: Clears the watch list. - -Examples: - -``seedwatch MUSHROOM_HELMET_PLUMP 30`` - add ``MUSHROOM_HELMET_PLUMP`` to the watch list, limit = 30 -``seedwatch MUSHROOM_HELMET_PLUMP`` - removes ``MUSHROOM_HELMET_PLUMP`` from the watch list. -``seedwatch all 30`` - adds all plants from the abbreviation list to the watch list, the limit being 30. - -.. _spotclean: - -spotclean -========= -Works like ``clean map snow mud``, but only for the tile under the cursor. Ideal -if you want to keep that bloody entrance ``clean map`` would clean up. - -:dfhack-keybind:`spotclean` - -.. _stockflow: - -stockflow -========= -Allows the fortress bookkeeper to queue jobs through the manager, -based on space or items available in stockpiles. - -Inspired by `workflow`. - -Usage: - -``stockflow enable`` - Enable the plugin. -``stockflow disable`` - Disable the plugin. -``stockflow fast`` - Enable the plugin in fast mode. -``stockflow list`` - List any work order settings for your stockpiles. -``stockflow status`` - Display whether the plugin is enabled. - -While enabled, the :kbd:`q` menu of each stockpile will have two new options: - -* :kbd:`j`: Select a job to order, from an interface like the manager's screen. -* :kbd:`J`: Cycle between several options for how many such jobs to order. - -Whenever the bookkeeper updates stockpile records, new work orders will -be placed on the manager's queue for each such selection, reduced by the -number of identical orders already in the queue. - -In fast mode, new work orders will be enqueued once per day, instead of -waiting for the bookkeeper. - -.. _tailor: - -tailor -====== - -Whenever the bookkeeper updates stockpile records, this plugin will scan every unit in the fort, -count up the number that are worn, and then order enough more made to replace all worn items. -If there are enough replacement items in inventory to replace all worn items, the units wearing them -will have the worn items confiscated (in the same manner as the `cleanowned` plugin) so that they'll -reeequip with replacement items. - -Use the `enable` and `disable ` commands to toggle this plugin's status, or run -``tailor status`` to check its current status. - -.. _workflow: - -workflow -======== -Manage control of repeat jobs. `gui/workflow` provides a simple -front-end integrated in the game UI. - -Usage: - -``workflow enable [option...], workflow disable [option...]`` - If no options are specified, enables or disables the plugin. - Otherwise, enables or disables any of the following options: - - - drybuckets: Automatically empty abandoned water buckets. - - auto-melt: Resume melt jobs when there are objects to melt. -``workflow jobs`` - List workflow-controlled jobs (if in a workshop, filtered by it). -``workflow list`` - List active constraints, and their job counts. -``workflow list-commands`` - List active constraints as workflow commands that re-create them; - this list can be copied to a file, and then reloaded using the - ``script`` built-in command. -``workflow count [cnt-gap]`` - Set a constraint, counting every stack as 1 item. -``workflow amount [cnt-gap]`` - Set a constraint, counting all items within stacks. -``workflow unlimit `` - Delete a constraint. -``workflow unlimit-all`` - Delete all constraints. - -Function --------- -When the plugin is enabled, it protects all repeat jobs from removal. -If they do disappear due to any cause, they are immediately re-added to their -workshop and suspended. - -In addition, when any constraints on item amounts are set, repeat jobs that -produce that kind of item are automatically suspended and resumed as the item -amount goes above or below the limit. The gap specifies how much below the limit -the amount has to drop before jobs are resumed; this is intended to reduce -the frequency of jobs being toggled. - -Constraint format ------------------ -The constraint spec consists of 4 parts, separated with ``/`` characters:: - - ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,] - -The first part is mandatory and specifies the item type and subtype, -using the raw tokens for items (the same syntax used custom reaction inputs). -For more information, see :wiki:`this wiki page `. - -The subsequent parts are optional: - -- A generic material spec constrains the item material to one of - the hard-coded generic classes, which currently include:: - - PLANT WOOD CLOTH SILK LEATHER BONE SHELL SOAP TOOTH HORN PEARL YARN - METAL STONE SAND GLASS CLAY MILK - -- A specific material spec chooses the material exactly, using the - raw syntax for reaction input materials, e.g. ``INORGANIC:IRON``, - although for convenience it also allows just ``IRON``, or ``ACACIA:WOOD`` etc. - See the link above for more details on the unabbreviated raw syntax. - -- A comma-separated list of miscellaneous flags, which currently can - be used to ignore imported items or items below a certain quality. - -Constraint examples -------------------- -Keep metal bolts within 900-1000, and wood/bone within 150-200:: - - workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100 - workflow amount AMMO:ITEM_AMMO_BOLTS/WOOD,BONE 200 50 - -Keep the number of prepared food & drink stacks between 90 and 120:: - - workflow count FOOD 120 30 - workflow count DRINK 120 30 - -Make sure there are always 25-30 empty bins/barrels/bags:: - - workflow count BIN 30 - workflow count BARREL 30 - workflow count BOX/CLOTH,SILK,YARN 30 - -Make sure there are always 15-20 coal and 25-30 copper bars:: - - workflow count BAR//COAL 20 - workflow count BAR//COPPER 30 - -Produce 15-20 gold crafts:: - - workflow count CRAFTS//GOLD 20 - -Collect 15-20 sand bags and clay boulders:: - - workflow count POWDER_MISC/SAND 20 - workflow count BOULDER/CLAY 20 - -Make sure there are always 80-100 units of dimple dye:: - - workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20 - -.. note:: - - In order for this to work, you have to set the material of the PLANT input - on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the `job item-material ` - command. Otherwise the plugin won't be able to deduce the output material. - -Maintain 10-100 locally-made crafts of exceptional quality:: - - workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90 - -.. _workNow: - -workNow -======= -Don't allow dwarves to idle if any jobs are available. - -When workNow is active, every time the game pauses, DF will make dwarves -perform any appropriate available jobs. This includes when you one step -through the game using the pause menu. Usage: - -:workNow: print workNow status -:workNow 0: deactivate workNow -:workNow 1: activate workNow (look for jobs on pause, and only then) -:workNow 2: make dwarves look for jobs whenever a job completes - -.. _zone: - -zone -==== -Helps a bit with managing activity zones (pens, pastures and pits) and cages. - -:dfhack-keybind:`zone` - -Options: - -:set: Set zone or cage under cursor as default for future assigns. -:assign: Assign unit(s) to the pen or pit marked with the 'set' command. - If no filters are set a unit must be selected in the in-game ui. - Can also be followed by a valid zone id which will be set - instead. -:unassign: Unassign selected creature from it's zone. -:nick: Mass-assign nicknames, must be followed by the name you want - to set. -:remnick: Mass-remove nicknames. -:enumnick: Assign enumerated nicknames (e.g. "Hen 1", "Hen 2"...). Must be - followed by the prefix to use in nicknames. -:tocages: Assign unit(s) to cages inside a pasture. -:uinfo: Print info about unit(s). If no filters are set a unit must - be selected in the in-game ui. -:zinfo: Print info about zone(s). If no filters are set zones under - the cursor are listed. -:verbose: Print some more info. -:filters: Print list of valid filter options. -:examples: Print some usage examples. -:not: Negates the next filter keyword. - -Filters: - -:all: Process all units (to be used with additional filters). -:count: Must be followed by a number. Process only n units (to be used - with additional filters). -:unassigned: Not assigned to zone, chain or built cage. -:minage: Minimum age. Must be followed by number. -:maxage: Maximum age. Must be followed by number. -:race: Must be followed by a race RAW ID (e.g. BIRD_TURKEY, ALPACA, - etc). Negatable. -:caged: In a built cage. Negatable. -:own: From own civilization. Negatable. -:merchant: Is a merchant / belongs to a merchant. Should only be used for - pitting, not for stealing animals (slaughter should work). -:war: Trained war creature. Negatable. -:hunting: Trained hunting creature. Negatable. -:tamed: Creature is tame. Negatable. -:trained: Creature is trained. Finds war/hunting creatures as well as - creatures who have a training level greater than 'domesticated'. - If you want to specifically search for war/hunting creatures use - 'war' or 'hunting' Negatable. -:trainablewar: Creature can be trained for war (and is not already trained for - war/hunt). Negatable. -:trainablehunt: Creature can be trained for hunting (and is not already trained - for war/hunt). Negatable. -:male: Creature is male. Negatable. -:female: Creature is female. Negatable. -:egglayer: Race lays eggs. Negatable. -:grazer: Race is a grazer. Negatable. -:milkable: Race is milkable. Negatable. - -Usage with single units ------------------------ -One convenient way to use the zone tool is to bind the command 'zone assign' to -a hotkey, maybe also the command 'zone set'. Place the in-game cursor over -a pen/pasture or pit, use 'zone set' to mark it. Then you can select units -on the map (in 'v' or 'k' mode), in the unit list or from inside cages -and use 'zone assign' to assign them to their new home. Allows pitting your -own dwarves, by the way. - -Usage with filters ------------------- -All filters can be used together with the 'assign' command. - -Restrictions: It's not possible to assign units who are inside built cages -or chained because in most cases that won't be desirable anyways. -It's not possible to cage owned pets because in that case the owner -uncages them after a while which results in infinite hauling back and forth. - -Usually you should always use the filter 'own' (which implies tame) unless you -want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless -you specify 'race DWARF' (so it's safe to use 'assign all own' to one big -pasture if you want to have all your animals at the same place). 'egglayer' and -'milkable' should be used together with 'female' unless you have a mod with -egg-laying male elves who give milk or whatever. Merchants and their animals are -ignored unless you specify 'merchant' (pitting them should be no problem, -but stealing and pasturing their animals is not a good idea since currently they -are not properly added to your own stocks; slaughtering them should work). - -Most filters can be negated (e.g. 'not grazer' -> race is not a grazer). - -Mass-renaming -------------- -Using the 'nick' command you can set the same nickname for multiple units. -If used without 'assign', 'all' or 'count' it will rename all units in the -current default target zone. Combined with 'assign', 'all' or 'count' (and -further optional filters) it will rename units matching the filter conditions. - -Cage zones ----------- -Using the 'tocages' command you can assign units to a set of cages, for example -a room next to your butcher shop(s). They will be spread evenly among available -cages to optimize hauling to and butchering from them. For this to work you need -to build cages and then place one pen/pasture activity zone above them, covering -all cages you want to use. Then use 'zone set' (like with 'assign') and use -'zone tocages filter1 filter2 ...'. 'tocages' overwrites 'assign' because it -would make no sense, but can be used together with 'nick' or 'remnick' and all -the usual filters. - -Examples --------- -``zone assign all own ALPACA minage 3 maxage 10`` - Assign all own alpacas who are between 3 and 10 years old to the selected - pasture. -``zone assign all own caged grazer nick ineedgrass`` - Assign all own grazers who are sitting in cages on stockpiles (e.g. after - buying them from merchants) to the selected pasture and give them - the nickname 'ineedgrass'. -``zone assign all own not grazer not race CAT`` - Assign all own animals who are not grazers, excluding cats. -``zone assign count 5 own female milkable`` - Assign up to 5 own female milkable creatures to the selected pasture. -``zone assign all own race DWARF maxage 2`` - Throw all useless kids into a pit :) -``zone nick donttouchme`` - Nicknames all units in the current default zone or cage to 'donttouchme'. - Mostly intended to be used for special pastures or cages which are not marked - as rooms you want to protect from autobutcher. -``zone tocages count 50 own tame male not grazer`` - Stuff up to 50 owned tame male animals who are not grazers into cages built - on the current default zone. - -================ -Map modification -================ - -.. contents:: - :local: - -.. _3dveins: - -3dveins -======= -Removes all existing veins from the map and generates new ones using -3D Perlin noise, in order to produce a layout that smoothly flows between -Z levels. The vein distribution is based on the world seed, so running -the command for the second time should produce no change. It is best to -run it just once immediately after embark. - -This command is intended as only a cosmetic change, so it takes -care to exactly preserve the mineral counts reported by `prospect` ``all``. -The amounts of different layer stones may slightly change in some cases -if vein mass shifts between Z layers. - -The only undo option is to restore your save from backup. - -.. _alltraffic: - -alltraffic -========== -Set traffic designations for every single tile of the map - useful for resetting -traffic designations. See also `filltraffic`, `restrictice`, and `restrictliquids`. - -Options: - -:H: High Traffic -:N: Normal Traffic -:L: Low Traffic -:R: Restricted Traffic - -.. _burrows: - -burrows -======= -Miscellaneous burrow control. Allows manipulating burrows and automated burrow -expansion while digging. - -Options: - -:enable feature ...: - Enable features of the plugin. -:disable feature ...: - Disable features of the plugin. -:clear-unit burrow burrow ...: - Remove all units from the burrows. -:clear-tiles burrow burrow ...: - Remove all tiles from the burrows. -:set-units target-burrow src-burrow ...: - Clear target, and adds units from source burrows. -:add-units target-burrow src-burrow ...: - Add units from the source burrows to the target. -:remove-units target-burrow src-burrow ...: - Remove units in source burrows from the target. -:set-tiles target-burrow src-burrow ...: - Clear target and adds tiles from the source burrows. -:add-tiles target-burrow src-burrow ...: - Add tiles from the source burrows to the target. -:remove-tiles target-burrow src-burrow ...: - Remove tiles in source burrows from the target. - - For these three options, in place of a source burrow it is - possible to use one of the following keywords: ABOVE_GROUND, - SUBTERRANEAN, INSIDE, OUTSIDE, LIGHT, DARK, HIDDEN, REVEALED - -Features: - -:auto-grow: When a wall inside a burrow with a name ending in '+' is dug - out, the burrow is extended to newly-revealed adjacent walls. - This final '+' may be omitted in burrow name args of commands above. - Digging 1-wide corridors with the miner inside the burrow is SLOW. - -.. _changeitem: - -changeitem -========== -Allows changing item material and base quality. By default the item currently -selected in the UI will be changed (you can select items in the 'k' list -or inside containers/inventory). By default change is only allowed if materials -is of the same subtype (for example wood<->wood, stone<->stone etc). But since -some transformations work pretty well and may be desired you can override this -with 'force'. Note that some attributes will not be touched, possibly resulting -in weirdness. To get an idea how the RAW id should look like, check some items -with 'info'. Using 'force' might create items which are not touched by -crafters/haulers. - -Options: - -:info: Don't change anything, print some info instead. -:here: Change all items at the cursor position. Requires in-game cursor. -:material, m: Change material. Must be followed by valid material RAW id. -:quality, q: Change base quality. Must be followed by number (0-5). -:force: Ignore subtypes, force change to new material. - -Examples: - -``changeitem m INORGANIC:GRANITE here`` - Change material of all items under the cursor to granite. -``changeitem q 5`` - Change currently selected item to masterpiece quality. - -.. _changelayer: - -changelayer -=========== -Changes material of the geology layer under cursor to the specified inorganic -RAW material. Can have impact on all surrounding regions, not only your embark! -By default changing stone to soil and vice versa is not allowed. By default -changes only the layer at the cursor position. Note that one layer can stretch -across lots of z levels. By default changes only the geology which is linked -to the biome under the cursor. That geology might be linked to other biomes -as well, though. Mineral veins and gem clusters will stay on the map. Use -`changevein` for them. - -tl;dr: You will end up with changing quite big areas in one go, especially if -you use it in lower z levels. Use with care. - -Options: - -:all_biomes: Change selected layer for all biomes on your map. - Result may be undesirable since the same layer can AND WILL - be on different z-levels for different biomes. Use the tool - 'probe' to get an idea how layers and biomes are distributed - on your map. -:all_layers: Change all layers on your map (only for the selected biome - unless 'all_biomes' is added). - Candy mountain, anyone? Will make your map quite boring, - but tidy. -:force: Allow changing stone to soil and vice versa. !!THIS CAN HAVE - WEIRD EFFECTS, USE WITH CARE!! - Note that soil will not be magically replaced with stone. - You will, however, get a stone floor after digging so it - will allow the floor to be engraved. - Note that stone will not be magically replaced with soil. - You will, however, get a soil floor after digging so it - could be helpful for creating farm plots on maps with no - soil. -:verbose: Give some details about what is being changed. -:trouble: Give some advice about known problems. - -Examples: - -``changelayer GRANITE`` - Convert layer at cursor position into granite. -``changelayer SILTY_CLAY force`` - Convert layer at cursor position into clay even if it's stone. -``changelayer MARBLE all_biomes all_layers`` - Convert all layers of all biomes which are not soil into marble. - -.. note:: - - * If you use changelayer and nothing happens, try to pause/unpause the game - for a while and try to move the cursor to another tile. Then try again. - If that doesn't help try temporarily changing some other layer, undo your - changes and try again for the layer you want to change. Saving - and reloading your map might also help. - * You should be fine if you only change single layers without the use - of 'force'. Still it's advisable to save your game before messing with - the map. - * When you force changelayer to convert soil to stone you might experience - weird stuff (flashing tiles, tiles changed all over place etc). - Try reverting the changes manually or even better use an older savegame. - You did save your game, right? - -.. _changevein: - -changevein -========== -Changes material of the vein under cursor to the specified inorganic RAW -material. Only affects tiles within the current 16x16 block - for veins and -large clusters, you will need to use this command multiple times. - -Example: - -``changevein NATIVE_PLATINUM`` - Convert vein at cursor position into platinum ore. - -.. _cleanconst: - -cleanconst -========== -Cleans up construction materials. - -This utility alters all constructions on the map so that they spawn their -building component when they are disassembled, allowing their actual -build items to be safely deleted. This can improve FPS in extreme situations. - -.. _deramp: - -deramp -====== -Removes all ramps designated for removal from the map. This is useful for -replicating the old channel digging designation. It also removes any and -all 'down ramps' that can remain after a cave-in (you don't have to designate -anything for that to happen). - -.. _dig: -.. _digv: -.. _digvx: -.. _digl: -.. _diglx: - -dig -=== -This plugin makes many automated or complicated dig patterns easy. - -Basic commands: - -:digv: Designate all of the selected vein for digging. -:digvx: Also cross z-levels, digging stairs as needed. Alias for ``digv x``. -:digl: Like ``digv``, for layer stone. Also supports an ``undo`` option - to remove designations, for if you accidentally set 50 levels at once. -:diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. - -:dfhack-keybind:`digv` - -.. note:: - - All commands implemented by the `dig` plugin (listed by ``ls dig``) support - specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, - where ``#`` is a number from 1 to 7. If a priority is not specified, the - priority selected in-game is used as the default. - -.. _digcircle: - -digcircle -========= -A command for easy designation of filled and hollow circles. -It has several types of options. - -Shape: - -:hollow: Set the circle to hollow (default) -:filled: Set the circle to filled -:#: Diameter in tiles (default = 0, does nothing) - -Action: - -:set: Set designation (default) -:unset: Unset current designation -:invert: Invert designations already present - -Designation types: - -:dig: Normal digging designation (default) -:ramp: Ramp digging -:ustair: Staircase up -:dstair: Staircase down -:xstair: Staircase up/down -:chan: Dig channel - -After you have set the options, the command called with no options -repeats with the last selected parameters. - -Examples: - -``digcircle filled 3`` - Dig a filled circle with diameter = 3. -``digcircle`` - Do it again. - -.. _digexp: - -digexp -====== -This command is for :wiki:`exploratory mining `. - -There are two variables that can be set: pattern and filter. - -Patterns: - -:diag5: diagonals separated by 5 tiles -:diag5r: diag5 rotated 90 degrees -:ladder: A 'ladder' pattern -:ladderr: ladder rotated 90 degrees -:clear: Just remove all dig designations -:cross: A cross, exactly in the middle of the map. - -Filters: - -:all: designate whole z-level -:hidden: designate only hidden tiles of z-level (default) -:designated: Take current designation and apply pattern to it. - -After you have a pattern set, you can use ``expdig`` to apply it again. - -Examples: - -``expdig diag5 hidden`` - Designate the diagonal 5 patter over all hidden tiles -``expdig`` - Apply last used pattern and filter -``expdig ladder designated`` - Take current designations and replace them with the ladder pattern - -.. _digFlood: - -digFlood -======== -Automatically digs out specified veins as they are discovered. It runs once -every time a dwarf finishes a dig job. It will only dig out appropriate tiles -that are adjacent to the finished dig job. To add a vein type, use ``digFlood 1 [type]``. -This will also enable the plugin. To remove a vein type, use ``digFlood 0 [type] 1`` -to disable, then remove, then re-enable. - -Usage: - -:help digflood: detailed help message -:digFlood 0: disable the plugin -:digFlood 1: enable the plugin -:digFlood 0 MICROCLINE COAL_BITUMINOUS 1: - disable plugin, remove microcline and bituminous coal from monitoring, then re-enable the plugin -:digFlood CLEAR: remove all inorganics from monitoring -:digFlood digAll1: ignore the monitor list and dig any vein -:digFlood digAll0: disable digAll mode - -.. _digtype: - -digtype -======= -For every tile on the map of the same vein type as the selected tile, -this command designates it to have the same designation as the -selected tile. If the selected tile has no designation, they will be -dig designated. -If an argument is given, the designation of the selected tile is -ignored, and all appropriate tiles are set to the specified -designation. - -Options: - -:dig: -:channel: -:ramp: -:updown: up/down stairs -:up: up stairs -:down: down stairs -:clear: clear designation - -.. _filltraffic: - -filltraffic -=========== -Set traffic designations using flood-fill starting at the cursor. -See also `alltraffic`, `restrictice`, and `restrictliquids`. Options: - -:H: High Traffic -:N: Normal Traffic -:L: Low Traffic -:R: Restricted Traffic -:X: Fill across z-levels. -:B: Include buildings and stockpiles. -:P: Include empty space. - -Example: - -``filltraffic H`` - When used in a room with doors, it will set traffic to HIGH in just that room. - -.. _getplants: - -getplants -========= -This tool allows plant gathering and tree cutting by RAW ID. Specify the types -of trees to cut down and/or shrubs to gather by their plant names, separated -by spaces. - -Options: - -:``-t``: Tree: Select trees only (exclude shrubs) -:``-s``: Shrub: Select shrubs only (exclude trees) -:``-f``: Farming: Designate only shrubs that yield seeds for farming. Implies -s -:``-c``: Clear: Clear designations instead of setting them -:``-x``: eXcept: Apply selected action to all plants except those specified (invert - selection) -:``-a``: All: Select every type of plant (obeys ``-t``/``-s``/``-f``) -:``-v``: Verbose: Lists the number of (un)designations per plant -:``-n *``: Number: Designate up to * (an integer number) plants of each species - -Specifying both ``-t`` and ``-s`` or ``-f`` will have no effect. If no plant IDs are -specified, all valid plant IDs will be listed, with ``-t``, ``-s``, and ``-f`` -restricting the list to trees, shrubs, and farmable shrubs, respectively. - -.. note:: - - DF is capable of determining that a shrub has already been picked, leaving - an unusable structure part behind. This plugin does not perform such a check - (as the location of the required information has not yet been identified). - This leads to some shrubs being designated when they shouldn't be, causing a - plant gatherer to walk there and do nothing (except clearing the - designation). See :issue:`1479` for details. - - The implementation another known deficiency: it's incapable of detecting that - raw definitions that specify a seed extraction reaction for the structural part - but has no other use for it cannot actually yield any seeds, as the part is - never used (parts of :bug:`6940`, e.g. Red Spinach), even though DF - collects it, unless there's a workshop reaction to do it (which there isn't - in vanilla). - -.. _infiniteSky: - -infiniteSky -=========== -Automatically allocates new z-levels of sky at the top of the map as you build up, -or on request allocates many levels all at once. - -Usage: - -``infiniteSky n`` - Raise the sky by n z-levels. -``infiniteSky enable/disable`` - Enables/disables monitoring of constructions. If you build anything in the second to highest z-level, it will allocate one more sky level. This is so you can continue to build stairs upward. - -.. warning:: - - :issue:`Sometimes <254>` new z-levels disappear and cause cave-ins. - Saving and loading after creating new z-levels should fix the problem. - -.. _liquids: - -liquids -======= -Allows adding magma, water and obsidian to the game. It replaces the normal -dfhack command line and can't be used from a hotkey. Settings will be remembered -as long as dfhack runs. Intended for use in combination with the command -``liquids-here`` (which can be bound to a hotkey). See also :issue:`80`. - -.. warning:: - - Spawning and deleting liquids can mess up pathing data and - temperatures (creating heat traps). You've been warned. - -.. note:: - - `gui/liquids` is an in-game UI for this script. - -Settings will be remembered until you quit DF. You can call `liquids-here` to execute -the last configured action, which is useful in combination with keybindings. - -Usage: point the DF cursor at a tile you want to modify and use the commands. - -If you only want to add or remove water or magma from one tile, -`source` may be easier to use. - -Commands --------- -Misc commands: - -:q: quit -:help, ?: print this list of commands -:: put liquid - -Modes: - -:m: switch to magma -:w: switch to water -:o: make obsidian wall instead -:of: make obsidian floors -:rs: make a river source -:f: flow bits only -:wclean: remove salt and stagnant flags from tiles - -Set-Modes and flow properties (only for magma/water): - -:s+: only add mode -:s.: set mode -:s-: only remove mode -:f+: make the spawned liquid flow -:f.: don't change flow state (read state in flow mode) -:f-: make the spawned liquid static - -Permaflow (only for water): - -:pf.: don't change permaflow state -:pf-: make the spawned liquid static -:pf[NS][EW]: make the spawned liquid permanently flow -:0-7: set liquid amount - -Brush size and shape: - -:p, point: Single tile -:r, range: Block with cursor at bottom north-west (any place, any size) -:block: DF map block with cursor in it (regular spaced 16x16x1 blocks) -:column: Column from cursor, up through free space -:flood: Flood-fill water tiles from cursor (only makes sense with wclean) - -.. _liquids-here: - -liquids-here ------------- -Run the liquid spawner with the current/last settings made in liquids (if no -settings in liquids were made it paints a point of 7/7 magma by default). - -Intended to be used as keybinding. Requires an active in-game cursor. - -.. _plant: - -plant -===== -A tool for creating shrubs, growing, or getting rid of them. - -Subcommands: - -:create: Creates a new sapling under the cursor. Takes a raw ID as argument - (e.g. TOWER_CAP). The cursor must be located on a dirt or grass floor tile. -:grow: Turns saplings into trees; under the cursor if a sapling is selected, - or every sapling on the map if the cursor is hidden. - -For mass effects, use one of the additional options: - -:shrubs: affect all shrubs on the map -:trees: affect all trees on the map -:all: affect every plant! - -.. _regrass: - -regrass -======= -Regrows all the grass. Not much to it ;) - -.. _restrictice: - -restrictice -=========== -Restrict traffic on all tiles on top of visible ice. -See also `alltraffic`, `filltraffic`, and `restrictliquids`. - -.. _restrictliquids: - -restrictliquids -=============== -Restrict traffic on all visible tiles with liquid. -See also `alltraffic`, `filltraffic`, and `restrictice`. - -.. _tiletypes: - -tiletypes -========= -Can be used for painting map tiles and is an interactive command, much like -`liquids`. Some properties of existing tiles can be looked up with `probe`. If -something goes wrong, `fixveins` may help. - -The tool works with two set of options and a brush. The brush determines which -tiles will be processed. First set of options is the filter, which can exclude -some of the tiles from the brush by looking at the tile properties. The second -set of options is the paint - this determines how the selected tiles are -changed. - -Both paint and filter can have many different properties including things like -general shape (WALL, FLOOR, etc.), general material (SOIL, STONE, MINERAL, -etc.), state of 'designated', 'hidden' and 'light' flags. - -The properties of filter and paint can be partially defined. This means that -you can for example turn all stone fortifications into floors, preserving the -material:: - - filter material STONE - filter shape FORTIFICATION - paint shape FLOOR - -Or turn mineral vein floors back into walls:: - - filter shape FLOOR - filter material MINERAL - paint shape WALL - -The tool also allows tweaking some tile flags:: - - paint hidden 1 - paint hidden 0 - -This will hide previously revealed tiles (or show hidden with the 0 option). - -More recently, the tool supports changing the base material of the tile to -an arbitrary stone from the raws, by creating new veins as required. Note -that this mode paints under ice and constructions, instead of overwriting -them. To enable, use:: - - paint stone MICROCLINE - -This mode is incompatible with the regular ``material`` setting, so changing -it cancels the specific stone selection:: - - paint material ANY - -Since different vein types have different drop rates, it is possible to choose -which one to use in painting:: - - paint veintype CLUSTER_SMALL - -When the chosen type is ``CLUSTER`` (the default), the tool may automatically -choose to use layer stone or lava stone instead of veins if its material matches -the desired one. - -Any paint or filter option (or the entire paint or filter) can be disabled entirely by using the ANY keyword:: - - paint hidden ANY - paint shape ANY - filter material any - filter shape any - filter any - -You can use several different brushes for painting tiles: - -:point: a single tile -:range: a rectangular range -:column: a column ranging from current cursor to the first solid tile above -:block: a DF map block - 16x16 tiles, in a regular grid - -Example:: - - range 10 10 1 - -This will change the brush to a rectangle spanning 10x10 tiles on one z-level. -The range starts at the position of the cursor and goes to the east, south and -up. - -For more details, use ``tiletypes help``. - -.. _tiletypes-command: - -tiletypes-command ------------------ -Runs tiletypes commands, separated by ``;``. This makes it possible to change -tiletypes modes from a hotkey or via dfhack-run. - -Example:: - - tiletypes-command p any ; p s wall ; p sp normal - -This resets the paint filter to unsmoothed walls. - -.. _tiletypes-here: - -tiletypes-here --------------- -Apply the current tiletypes options at the in-game cursor position, including -the brush. Can be used from a hotkey. - -Options: - -:``-c``, ``--cursor ,,``: - Use the specified map coordinates instead of the current cursor position. If - this option is specified, then an active game map cursor is not necessary. -:``-h``, ``--help``: - Show command help text. -:``-q``, ``--quiet``: - Suppress non-error status output. - -.. _tiletypes-here-point: - -tiletypes-here-point --------------------- -Apply the current tiletypes options at the in-game cursor position to a single -tile. Can be used from a hotkey. - -This command supports the same options as `tiletypes-here` above. - -.. _tubefill: - -tubefill -======== -Fills all the adamantine veins again. Veins that were hollow will be left -alone. - -Options: - -:hollow: fill in naturally hollow veins too - -Beware that filling in hollow veins will trigger a demon invasion on top of -your miner when you dig into the region that used to be hollow. - - - -================= -Mods and Cheating -================= - -.. contents:: - :local: - -.. _add-spatter: - -add-spatter -=========== -This plugin makes reactions with names starting with ``SPATTER_ADD_`` -produce contaminants on the items instead of improvements. The plugin is -intended to give some use to all those poisons that can be bought from caravans, -so they're immune to being washed away by water or destroyed by `clean`. - -.. _adv-bodyswap: - -adv-bodyswap -============ -This allows taking control over your followers and other creatures in adventure -mode. For example, you can make them pick up new arms and armor and equip them -properly. - -Usage: - -* When viewing unit details, body-swaps into that unit. -* In the main adventure mode screen, reverts transient swap. - -:dfhack-keybind:`adv-bodyswap` - -.. _createitem: - -createitem -========== -Allows creating new items of arbitrary types and made of arbitrary materials. A -unit must be selected in-game to use this command. By default, items created are -spawned at the feet of the selected unit. - -Specify the item and material information as you would indicate them in -custom reaction raws, with the following differences: - -* Separate the item and material with a space rather than a colon -* If the item has no subtype, the ``:NONE`` can be omitted -* If the item is ``REMAINS``, ``FISH``, ``FISH_RAW``, ``VERMIN``, ``PET``, or ``EGG``, - specify a ``CREATURE:CASTE`` pair instead of a material token. -* If the item is a ``PLANT_GROWTH``, specify a ``PLANT_ID:GROWTH_ID`` pair - instead of a material token. - -Corpses, body parts, and prepared meals cannot be created using this tool. - -To obtain the item and material tokens of an existing item, run -``createitem inspect``. Its output can be passed directly as arguments to -``createitem`` to create new matching items, as long as the item type is -supported. - -Examples: - -* Create 2 pairs of steel gauntlets:: - - createitem GLOVES:ITEM_GLOVES_GAUNTLETS INORGANIC:STEEL 2 - -* Create tower-cap logs:: - - createitem WOOD PLANT_MAT:TOWER_CAP:WOOD - -* Create bilberries:: - - createitem PLANT_GROWTH BILBERRY:FRUIT - -For more examples, :wiki:`see this wiki page `. - -To change where new items are placed, first run the command with a -destination type while an appropriate destination is selected. - -Options: - -:floor: Subsequent items will be placed on the floor beneath the selected unit's feet. -:item: Subsequent items will be stored inside the currently selected item. -:building: Subsequent items will become part of the currently selected building. - Good for loading traps; do not use with workshops (or deconstruct to use the item). - -.. _dig-now: - -dig-now -======= - -Instantly completes non-marker dig designations, modifying tile shapes and -creating boulders, ores, and gems as if a miner were doing the mining or -engraving. By default, the entire map is processed and boulder generation -follows standard game rules, but the behavior is configurable. - -Note that no units will get mining or engraving experience for the dug/engraved -tiles. - -Trees and roots are not currently handled by this plugin and will be skipped. -Requests for engravings are also skipped since they would depend on the skill -and creative choices of individual engravers. Other types of engraving (i.e. -smoothing and track carving) are handled. - -Usage:: - - dig-now [ []] [] - -Where the optional ```` pair can be used to specify the coordinate bounds -within which ``dig-now`` will operate. If they are not specified, ``dig-now`` -will scan the entire map. If only one ```` is specified, only the tile at -that coordinate is processed. - -Any ```` parameters can either be an ``,,`` triple (e.g. -``35,12,150``) or the string ``here``, which means the position of the active -game cursor should be used. - -Examples: - -``dig-now`` - Dig designated tiles according to standard game rules. - -``dig-now --clean`` - Dig designated tiles, but don't generate any boulders, ores, or gems. - -``dig-now --dump here`` - Dig tiles and dump all generated boulders, ores, and gems at the tile under - the game cursor. - -Options: - -:``-c``, ``--clean``: - Don't generate any boulders, ores, or gems. Equivalent to - ``--percentages 0,0,0,0``. -:``-d``, ``--dump ``: - Dump any generated items at the specified coordinates. If the tile at those - coordinates is open space or is a wall, items will be generated on the - closest walkable tile below. -:``-e``, ``--everywhere``: - Generate a boulder, ore, or gem for every tile that can produce one. - Equivalent to ``--percentages 100,100,100,100``. -:``-h``, ``--help``: - Show quick usage help text. -:``-p``, ``--percentages ,,,``: - Set item generation percentages for each of the tile categories. The - ``vein`` category includes both the large oval clusters and the long stringy - mineral veins. Default is ``25,33,100,100``. -:``-z``, ``--cur-zlevel``: - Restricts the bounds to the currently visible z-level. - -.. _diggingInvaders: - -diggingInvaders -=============== -Makes invaders dig or destroy constructions to get to your dwarves. - -To enable/disable the pluging, use: ``diggingInvaders (1|enable)|(0|disable)`` - -Basic usage: - -:add GOBLIN: registers the race GOBLIN as a digging invader. Case-sensitive. -:remove GOBLIN: unregisters the race GOBLIN as a digging invader. Case-sensitive. -:now: makes invaders try to dig now, if plugin is enabled -:clear: clears all digging invader races -:edgesPerTick n: makes the pathfinding algorithm work on at most n edges per tick. - Set to 0 or lower to make it unlimited. - -You can also use ``diggingInvaders setCost (race) (action) n`` to set the -pathing cost of particular action, or ``setDelay`` to set how long it takes. -Costs and delays are per-tile, and the table shows default values. - -============================== ======= ====== ================================= -Action Cost Delay Notes -============================== ======= ====== ================================= -``walk`` 1 0 base cost in the path algorithm -``destroyBuilding`` 2 1,000 delay adds to the job_completion_timer of destroy building jobs that are assigned to invaders -``dig`` 10,000 1,000 digging soil or natural stone -``destroyRoughConstruction`` 1,000 1,000 constructions made from boulders -``destroySmoothConstruction`` 100 100 constructions made from blocks or bars -============================== ======= ====== ================================= - - -.. _fastdwarf: - -fastdwarf -========= -Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly -and perform tasks quickly. Teledwarf makes dwarves move instantaneously, -but do jobs at the same speed. - -:fastdwarf 0: disables both (also ``0 0``) -:fastdwarf 1: enables speedydwarf and disables teledwarf (also ``1 0``) -:fastdwarf 2: sets a native debug flag in the game memory that implements an - even more aggressive version of speedydwarf. -:fastdwarf 0 1: disables speedydwarf and enables teledwarf -:fastdwarf 1 1: enables both - -See `superdwarf` for a per-creature version. - -.. _forceequip: - -forceequip -========== -Forceequip moves local items into a unit's inventory. It is typically used to -equip specific clothing/armor items onto a dwarf, but can also be used to put -armor onto a war animal or to add unusual items (such as crowns) to any unit. - -For more information run ``forceequip help``. See also `modtools/equip-item`. - -.. _generated-creature-renamer: - -generated-creature-renamer -========================== -Automatically renames generated creatures, such as forgotten beasts, titans, -etc, to have raw token names that match the description given in-game. - -The ``list-generated`` command can be used to list the token names of all -generated creatures in a given save, with an optional ``detailed`` argument -to show the accompanying description. - -The ``save-generated-raws`` command will save a sample creature graphics file in -the Dwarf Fortress root directory, to use as a start for making a graphics set -for generated creatures using the new names that they get with this plugin. - -The new names are saved with the save, and the plugin, when enabled, only runs once -per save, unless there's an update. - -.. _lair: - -lair -==== -This command allows you to mark the map as a monster lair, preventing item -scatter on abandon. When invoked as ``lair reset``, it does the opposite. - -Unlike `reveal`, this command doesn't save the information about tiles - you -won't be able to restore state of real monster lairs using ``lair reset``. - -Options: - -:lair: Mark the map as monster lair -:lair reset: Mark the map as ordinary (not lair) - -.. _misery: - -misery -====== -When enabled, fake bad thoughts will be added to all dwarves. - -Usage: - -:misery enable n: enable misery with optional magnitude n. If specified, n must - be positive. -:misery n: same as "misery enable n" -:misery enable: same as "misery enable 1" -:misery disable: stop adding new negative thoughts. This will not remove - existing negative thoughts. Equivalent to "misery 0". -:misery clear: remove fake thoughts, even after saving and reloading. Does - not change factor. - -.. _mode: - -mode -==== -This command lets you see and change the game mode directly. - -.. warning:: - - Only use ``mode`` after making a backup of your save! - - Not all combinations are good for every situation and most of them will - produce undesirable results. There are a few good ones though. - -Examples: - - * You are in fort game mode, managing your fortress and paused. - * You switch to the arena game mode, *assume control of a creature* and then - * switch to adventure game mode(1). - You just lost a fortress and gained an adventurer. Alternatively: - - * You are in fort game mode, managing your fortress and paused at the esc menu. - * You switch to the adventure game mode, assume control of a creature, then save or retire. - * You just created a returnable mountain home and gained an adventurer. - -.. _power-meter: - -power-meter -=========== -The power-meter plugin implements a modified pressure plate that detects power being -supplied to gear boxes built in the four adjacent N/S/W/E tiles. - -The configuration front-end is implemented by `gui/power-meter`. - -.. _siege-engine: - -siege-engine -============ -Siege engines in DF haven't been updated since the game was 2D, and can -only aim in four directions. To make them useful above-ground, -this plugin allows you to: - -* link siege engines to stockpiles -* restrict operator skill levels (like workshops) -* load any object into a catapult, not just stones -* aim at a rectangular area in any direction, and across Z-levels - -The front-end is implemented by `gui/siege-engine`. - -.. _steam-engine: - -steam-engine -============ -The steam-engine plugin detects custom workshops with STEAM_ENGINE in -their token, and turns them into real steam engines. - -The vanilla game contains only water wheels and windmills as sources of -power, but windmills give relatively little power, and water wheels require -flowing water, which must either be a real river and thus immovable and -limited in supply, or actually flowing and thus laggy. - -Compared to the :wiki:`water reactor ` -exploit, steam engines make a lot of sense! - -Construction ------------- -The workshop needs water as its input, which it takes via a -passable floor tile below it, like usual magma workshops do. -The magma version also needs magma. - -Due to DFHack limits, the workshop will collapse over true open space. -However down stairs are passable but support machines, so you can use them. - -After constructing the building itself, machines can be connected -to the edge tiles that look like gear boxes. Their exact position -is extracted from the workshop raws. - -Like with collapse above, due to DFHack limits the workshop -can only immediately connect to machine components built AFTER it. -This also means that engines cannot be chained without intermediate -axles built after both engines. - -Operation ---------- -In order to operate the engine, queue the Stoke Boiler job (optionally -on repeat). A furnace operator will come, possibly bringing a bar of fuel, -and perform it. As a result, a "boiling water" item will appear -in the :kbd:`t` view of the workshop. - -.. note:: - - The completion of the job will actually consume one unit - of the appropriate liquids from below the workshop. This means - that you cannot just raise 7 units of magma with a piston and - have infinite power. However, liquid consumption should be slow - enough that water can be supplied by a pond zone bucket chain. - -Every such item gives 100 power, up to a limit of 300 for coal, -and 500 for a magma engine. The building can host twice that -amount of items to provide longer autonomous running. When the -boiler gets filled to capacity, all queued jobs are suspended; -once it drops back to 3+1 or 5+1 items, they are re-enabled. - -While the engine is providing power, steam is being consumed. -The consumption speed includes a fixed 10% waste rate, and -the remaining 90% are applied proportionally to the actual -load in the machine. With the engine at nominal 300 power with -150 load in the system, it will consume steam for actual -300*(10% + 90%*150/300) = 165 power. - -Masterpiece mechanism and chain will decrease the mechanical -power drawn by the engine itself from 10 to 5. Masterpiece -barrel decreases waste rate by 4%. Masterpiece piston and pipe -decrease it by further 4%, and also decrease the whole steam -use rate by 10%. - -Explosions ----------- -The engine must be constructed using barrel, pipe and piston -from fire-safe, or in the magma version magma-safe metals. - -During operation weak parts get gradually worn out, and -eventually the engine explodes. It should also explode if -toppled during operation by a building destroyer, or a -tantruming dwarf. - -Save files ----------- -It should be safe to load and view engine-using fortresses -from a DF version without DFHack installed, except that in such -case the engines won't work. However actually making modifications -to them, or machines they connect to (including by pulling levers), -can easily result in inconsistent state once this plugin is -available again. The effects may be as weird as negative power -being generated. - -.. _strangemood: - -strangemood -=========== -Creates a strange mood job the same way the game itself normally does it. - -Options: - -:-force: Ignore normal strange mood preconditions (no recent mood, minimum - moodable population, artifact limit not reached). -:-unit: Make the strange mood strike the selected unit instead of picking - one randomly. Unit eligibility is still enforced. -:-type : Force the mood to be of a particular type instead of choosing randomly based on happiness. - Valid values for T are "fey", "secretive", "possessed", "fell", and "macabre". -:-skill S: Force the mood to use a specific skill instead of choosing the highest moodable skill. - Valid values are "miner", "carpenter", "engraver", "mason", "tanner", "weaver", - "clothier", "weaponsmith", "armorsmith", "metalsmith", "gemcutter", "gemsetter", - "woodcrafter", "stonecrafter", "metalcrafter", "glassmaker", "leatherworker", - "bonecarver", "bowyer", and "mechanic". - -Known limitations: if the selected unit is currently performing a job, the mood will not be started. - -============== -Plugin Lua API -============== - -Some plugins consist solely of native libraries exposed to Lua. They are listed -in the `lua-api` file under `lua-plugins`: - -* `building-hacks` -* `cxxrandom` -* `eventful` -* `luasocket` -* `map-render` -* `pathable` -* `xlsxreader` - -=========== -UI Upgrades -=========== - -.. note:: - In order to avoid user confusion, as a matter of policy all GUI tools - display the word :guilabel:`DFHack` on the screen somewhere while active. - - When that is not appropriate because they merely add keybinding hints to - existing DF screens, they deliberately use red instead of green for the key. - -.. contents:: - :local: - - -.. _automaterial: - -automaterial -============ -This makes building constructions (walls, floors, fortifications, etc) a little bit -easier by saving you from having to trawl through long lists of materials each time -you place one. - -Firstly, it moves the last used material for a given construction type to the top of -the list, if there are any left. So if you build a wall with chalk blocks, the next -time you place a wall the chalk blocks will be at the top of the list, regardless of -distance (it only does this in "grouped" mode, as individual item lists could be huge). -This should mean you can place most constructions without having to search for your -preferred material type. - -.. image:: images/automaterial-mat.png - -Pressing :kbd:`a` while highlighting any material will enable that material for "auto select" -for this construction type. You can enable multiple materials as autoselect. Now the next -time you place this type of construction, the plugin will automatically choose materials -for you from the kinds you enabled. If there is enough to satisfy the whole placement, -you won't be prompted with the material screen - the construction will be placed and you -will be back in the construction menu as if you did it manually. - -When choosing the construction placement, you will see a couple of options: - -.. image:: images/automaterial-pos.png - -Use :kbd:`a` here to temporarily disable the material autoselection, e.g. if you need -to go to the material selection screen so you can toggle some materials on or off. - -The other option (auto type selection, off by default) can be toggled on with :kbd:`t`. If you -toggle this option on, instead of returning you to the main construction menu after selecting -materials, it returns you back to this screen. If you use this along with several autoselect -enabled materials, you should be able to place complex constructions more conveniently. - -.. _automelt: - -automelt -======== -When automelt is enabled for a stockpile, any meltable items placed -in it will be designated to be melted. -This plugin adds an option to the :kbd:`q` menu when `enabled `. - -.. _autotrade: - -autotrade -========= -When autotrade is enabled for a stockpile, any items placed in it will be -designated to be taken to the Trade Depot whenever merchants are on the map. -This plugin adds an option to the :kbd:`q` menu when `enabled `. - -.. _buildingplan: - -buildingplan -============ -When active (via ``enable buildingplan``), this plugin adds a planning mode for -building placement. You can then place furniture, constructions, and other buildings -before the required materials are available, and they will be created in a suspended -state. Buildingplan will periodically scan for appropriate items, and the jobs will -be unsuspended when the items are available. - -This is very useful when combined with `workflow` - you can set a constraint -to always have one or two doors/beds/tables/chairs/etc available, and place -as many as you like. The plugins then take over and fulfill the orders, -with minimal space dedicated to stockpiles. - -.. _buildingplan-filters: - -Item filtering --------------- - -While placing a building, you can set filters for what materials you want the building made -out of, what quality you want the component items to be, and whether you want the items to -be decorated. - -If a building type takes more than one item to construct, use :kbd:`Ctrl`:kbd:`Left` and -:kbd:`Ctrl`:kbd:`Right` to select the item that you want to set filters for. Any filters that -you set will be used for all buildings of the selected type placed from that point onward -(until you set a new filter or clear the current one). Buildings placed before the filters -were changed will keep the filter values that were set when the building was placed. - -For example, you can be sure that all your constructed walls are the same color by setting -a filter to accept only certain types of stone. - -Quickfort mode --------------- - -If you use the external Python Quickfort to apply building blueprints instead of the native -DFHack `quickfort` script, you must enable Quickfort mode. This temporarily enables -buildingplan for all building types and adds an extra blank screen after every building -placement. This "dummy" screen is needed for Python Quickfort to interact successfully with -Dwarf Fortress. - -Note that Quickfort mode is only for compatibility with the legacy Python Quickfort. The -DFHack `quickfort` script does not need Quickfort mode to be enabled. The `quickfort` script -will successfully integrate with buildingplan as long as the buildingplan plugin is enabled. - -.. _buildingplan-settings: - -Global settings ---------------- - -The buildingplan plugin has several global settings that can be set from the UI (:kbd:`G` -from any building placement screen, for example: :kbd:`b`:kbd:`a`:kbd:`G`). These settings -can also be set from the ``DFHack#`` prompt once a map is loaded (or from your -``onMapLoad.init`` file) with the syntax:: - - buildingplan set - -and displayed with:: - - buildingplan set - -The available settings are: - -+----------------+---------+-----------+---------------------------------------+ -| Setting | Default | Persisted | Description | -+================+=========+===========+=======================================+ -| all_enabled | false | no | Enable planning mode for all building | -| | | | types. | -+----------------+---------+-----------+---------------------------------------+ -| blocks | true | yes | Allow blocks, boulders, logs, or bars | -+----------------+---------+ | to be matched for generic "building | -| boulders | true | | material" items | -+----------------+---------+ | | -| logs | true | | | -+----------------+---------+ | | -| bars | false | | | -+----------------+---------+-----------+---------------------------------------+ -| quickfort_mode | false | no | Enable compatibility mode for the | -| | | | legacy Python Quickfort (not required | -| | | | for DFHack quickfort) | -+----------------+---------+-----------+---------------------------------------+ - -For example, to ensure you only use blocks when a "building material" item is required, you -could add this to your ``onMapLoad.init`` file:: - - on-new-fortress buildingplan set boulders false; buildingplan set logs false - -Persisted settings (i.e. ``blocks``, ``boulders``, ``logs``, and ``bars``) are saved with -your game, so you only need to set them to the values you want once. - -.. _command-prompt: - -command-prompt -============== -An in-game DFHack terminal, where you can enter other commands. - -:dfhack-keybind:`command-prompt` - -Usage: ``command-prompt [entry]`` - -If called with an entry, it starts with that text filled in. -Most useful for developers, who can set a keybinding to open -a laungage interpreter for lua or Ruby by starting with the -`:lua ` or `:rb ` commands. - -Otherwise somewhat similar to `gui/quickcmd`. - -.. image:: images/command-prompt.png - -.. _confirm: - -confirm -======= -Implements several confirmation dialogs for potentially destructive actions -(for example, seizing goods from traders or deleting hauling routes). - -Usage: - -:enable confirm: Enable all confirmations; alias ``confirm enable all``. - Replace with ``disable`` to disable. -:confirm help: List available confirmation dialogues. -:confirm enable option1 [option2...]: - Enable (or disable) specific confirmation dialogues. - -.. _debug: - -debug -===== -Manager for DFHack runtime debug prints. Debug prints are grouped by plugin name, -category name and print level. Levels are ``trace``, ``debug``, ``info``, -``warning`` and ``error``. - -The runtime message printing is controlled using filters. Filters set the -visible messages of all matching categories. Matching uses regular expression syntax, -which allows listing multiple alternative matches or partial name matches. -This syntax is a C++ version of the ECMA-262 grammar (Javascript regular expressions). -Details of differences can be found at -https://en.cppreference.com/w/cpp/regex/ecmascript - -Persistent filters are stored in ``dfhack-config/runtime-debug.json``. -Oldest filters are applied first. That means a newer filter can override the -older printing level selection. - -Usage: ``debugfilter [subcommand] [parameters...]`` - -The following subcommands are supported: - -help ----- -Give overall help or a detailed help for a subcommand. - -Usage: ``debugfilter help [subcommand]`` - -category --------- -List available debug plugin and category names. - -Usage: ``debugfilter category [plugin regex] [category regex]`` - -The list can be filtered using optional regex parameters. If filters aren't -given then the it uses ``"."`` regex which matches any character. The regex -parameters are good way to test regex before passing them to ``set``. - -filter ------- -List active and passive debug print level changes. - -Usage: ``debugfilter filter [id]`` - -Optional ``id`` parameter is the id listed as first column in the filter list. -If id is given then the command shows information for the given filter only in -multi line format that is better format if filter has long regex. - -set ---- -Creates a new debug filter to set category printing levels. - -Usage: ``debugfilter set [level] [plugin regex] [category regex]`` - -Adds a filter that will be deleted when DF process exists or plugin is unloaded. - -Usage: ``debugfilter set persistent [level] [plugin regex] [category regex]`` - -Stores the filter in the configuration file to until ``unset`` is used to remove -it. - -Level is the minimum debug printing level to show in log. - -* ``trace``: Possibly very noisy messages which can be printed many times per second - -* ``debug``: Messages that happen often but they should happen only a couple of times per second - -* ``info``: Important state changes that happen rarely during normal execution - -* ``warning``: Enabled by default. Shows warnings about unexpected events which code managed to handle correctly. - -* ``error``: Enabled by default. Shows errors which code can't handle without user intervention. - -unset ------ -Delete a space separated list of filters - -Usage: ``debugfilter unset [id...]`` - -disable -------- -Disable a space separated list of filters but keep it in the filter list - -Usage: ``debugfilter disable [id...]`` - -enable ------- -Enable a space sperate list of filters - -Usage: ``debugfilter enable [id...]`` - -.. _embark-assistant: - -embark-assistant -================ - -This plugin provides embark site selection help. It has to be run with the -``embark-assistant`` command while the pre-embark screen is displayed and shows -extended (and correct(?)) resource information for the embark rectangle as well -as normally undisplayed sites in the current embark region. It also has a site -selection tool with more options than DF's vanilla search tool. For detailed -help invoke the in game info screen. - -.. _embark-tools: - -embark-tools -============ -A collection of embark-related tools. Usage and available tools:: - - embark-tools enable/disable tool [tool]... - -:anywhere: Allows embarking anywhere (including sites, mountain-only biomes, - and oceans). Use with caution. -:mouse: Implements mouse controls (currently in the local embark region only) -:sand: Displays an indicator when sand is present in the currently-selected - area, similar to the default clay/stone indicators. -:sticky: Maintains the selected local area while navigating the world map - -.. _follow: - -follow -====== -Makes the game view follow the currently highlighted unit after you exit from the -current menu or cursor mode. Handy for watching dwarves running around. Deactivated -by moving the view manually. - -.. _hotkeys: - -hotkeys -======= -Opens an in-game screen showing which DFHack keybindings are -active in the current context. See also `hotkey-notes`. - -.. image:: images/hotkeys.png - -:dfhack-keybind:`hotkeys` - -.. _manipulator: - -manipulator -=========== -An in-game equivalent to the popular program Dwarf Therapist. - -To activate, open the unit screen and press :kbd:`l`. - -.. image:: images/manipulator.png - -The far left column displays the unit's Happiness (color-coded based on its -value), Name, Profession/Squad, and the right half of the screen displays each -dwarf's labor settings and skill levels (0-9 for Dabbling through Professional, -A-E for Great through Grand Master, and U-Z for Legendary through Legendary+5). - -Cells with teal backgrounds denote skills not controlled by labors, e.g. -military and social skills. - -.. image:: images/manipulator2.png - -Press :kbd:`t` to toggle between Profession, Squad, and Job views. - -.. image:: images/manipulator3.png - -Use the arrow keys or number pad to move the cursor around, holding :kbd:`Shift` to -move 10 tiles at a time. - -Press the Z-Up (:kbd:`<`) and Z-Down (:kbd:`>`) keys to move quickly between labor/skill -categories. The numpad Z-Up and Z-Down keys seek to the first or last unit -in the list. :kbd:`Backspace` seeks to the top left corner. - -Press Enter to toggle the selected labor for the selected unit, or Shift+Enter -to toggle all labors within the selected category. - -Press the :kbd:`+`:kbd:`-` keys to sort the unit list according to the currently selected -skill/labor, and press the :kbd:`*`:kbd:`/` keys to sort the unit list by Name, Profession/Squad, -Happiness, or Arrival order (using :kbd:`Tab` to select which sort method to use here). - -With a unit selected, you can press the :kbd:`v` key to view its properties (and -possibly set a custom nickname or profession) or the :kbd:`c` key to exit -Manipulator and zoom to its position within your fortress. - -The following mouse shortcuts are also available: - -* Click on a column header to sort the unit list. Left-click to sort it in one - direction (descending for happiness or labors/skills, ascending for name, - profession or squad) and right-click to sort it in the opposite direction. -* Left-click on a labor cell to toggle that labor. Right-click to move the - cursor onto that cell instead of toggling it. -* Left-click on a unit's name, profession or squad to view its properties. -* Right-click on a unit's name, profession or squad to zoom to it. - -Pressing :kbd:`Esc` normally returns to the unit screen, but :kbd:`Shift`:kbd:`Esc` would exit -directly to the main dwarf mode screen. - -Professions ------------ - -The manipulator plugin supports saving professions: a named set of labors that can be -quickly applied to one or multiple dwarves. - -To save a profession, highlight a dwarf and press :kbd:`P`. The profession will be saved using -the custom profession name of the dwarf, or the default for that dwarf if no custom profession -name has been set. - -To apply a profession, either highlight a single dwarf or select multiple with -:kbd:`x`, and press :kbd:`p` to select the profession to apply. All labors for -the selected dwarves will be reset to the labors of the chosen profession. - -Professions are saved as human-readable text files in the -``dfhack-config/professions`` folder within the DF folder, and can be edited or -deleted there. - -The professions library -~~~~~~~~~~~~~~~~~~~~~~~ - -The manipulator plugin comes with a library of professions that you can assign -to your dwarves. - -If you'd rather use Dwarf Therapist to manage your labors, it is easy to import -these professions to DT and use them there. Simply assign the professions you -want to import to a dwarf. Once you have assigned a profession to at least one -dwarf, you can select "Import Professions from DF" in the DT "File" menu. The -professions will then be available for use in DT. - -In the charts below, the "At Start" and "Max" columns indicate the approximate -number of dwarves of each profession that you are likely to need at the start of -the game and how many you are likely to need in a mature fort. These are just -approximations. Your playstyle may demand more or fewer of each profession. - -============= ======== ===== ================================================= -Profession At Start Max Description -============= ======== ===== ================================================= -Chef 0 3 Buchery, Tanning, and Cooking. It is important to - focus just a few dwarves on cooking since - well-crafted meals make dwarves very happy. They - are also an excellent trade good. -Craftsdwarf 0 4-6 All labors used at Craftsdwarf's workshops, - Glassmaker's workshops, and kilns. -Doctor 0 2-4 The full suite of medical labors, plus Animal - Caretaking for those using the dwarfvet plugin. -Farmer 1 4 Food- and animal product-related labors. This - profession also has the ``Alchemist`` labor - enabled since they need to focus on food-related - jobs, though you might want to disable - ``Alchemist`` for your first farmer until there - are actual farming duties to perform. -Fisherdwarf 0 0-1 Fishing and fish cleaning. If you assign this - profession to any dwarf, be prepared to be - inundated with fish. Fisherdwarves *never stop - fishing*. Be sure to also run ``prioritize -a - PrepareRawFish ExtractFromRawFish`` or else - caught fish will just be left to rot. -Hauler 0 >20 All hauling labors plus Siege Operating, Mechanic - (so haulers can assist in reloading traps) and - Architecture (so haulers can help build massive - windmill farms and pump stacks). As you - accumulate enough Haulers, you can turn off - hauling labors for other dwarves so they can - focus on their skilled tasks. You may also want - to restrict your Mechanic's workshops to only - skilled mechanics so your haulers don't make - low-quality mechanisms. -Laborer 0 10-12 All labors that don't improve quality with skill, - such as Soapmaking and furnace labors. -Marksdwarf 0 10-30 Similar to Hauler. See the description for - Meleedwarf below for more details. -Mason 2 2-4 Masonry and Gem Cutting/Encrusting. In the early - game, you may need to run "`prioritize` - ConstructBuilding" to get your masons to build - wells and bridges if they are too busy crafting - stone furniture. -Meleedwarf 0 20-50 Similar to Hauler, but without most civilian - labors. This profession is separate from Hauler - so you can find your military dwarves easily. - Meleedwarves and Marksdwarves have Mechanics and - hauling labors enabled so you can temporarily - deactivate your military after sieges and allow - your military dwarves to help clean up. -Migrant 0 0 You can assign this profession to new migrants - temporarily while you sort them into professions. - Like Marksdwarf and Meleedwarf, the purpose of - this profession is so you can find your new - dwarves more easily. -Miner 2 2-10 Mining and Engraving. This profession also has - the ``Alchemist`` labor enabled, which disables - hauling for those using the `autohauler` plugin. - Once the need for Miners tapers off in the late - game, dwarves with this profession make good - military dwarves, wielding their picks as - weapons. -Outdoorsdwarf 1 2-4 Carpentry, Bowyery, Woodcutting, Animal Training, - Trapping, Plant Gathering, Beekeeping, and Siege - Engineering. -Smith 0 2-4 Smithing labors. You may want to specialize your - Smiths to focus on a single smithing skill to - maximize equipment quality. -StartManager 1 0 All skills not covered by the other starting - professions (Miner, Mason, Outdoorsdwarf, and - Farmer), plus a few overlapping skills to - assist in critical tasks at the beginning of the - game. Individual labors should be turned off as - migrants are assigned more specialized - professions that cover them, and the StartManager - dwarf can eventually convert to some other - profession. -Tailor 0 2 Textile industry labors: Dying, Leatherworking, - Weaving, and Clothesmaking. -============= ======== ===== ================================================= - -A note on autohauler -~~~~~~~~~~~~~~~~~~~~ - -These profession definitions are designed to work well with or without the -`autohauler` plugin (which helps to keep your dwarves focused on skilled labors -instead of constantly being distracted by hauling). If you do want to use -autohauler, adding the following lines to your ``onMapLoad.init`` file will -configure it to let the professions manage the "Feed water to civilians" and -"Recover wounded" labors instead of enabling those labors for all hauling -dwarves:: - - on-new-fortress enable autohauler - on-new-fortress autohauler FEED_WATER_CIVILIANS allow - on-new-fortress autohauler RECOVER_WOUNDED allow - -.. _mousequery: - -mousequery -========== -Adds mouse controls to the DF interface, e.g. click-and-drag designations. - -Options: - -:plugin: enable/disable the entire plugin -:rbutton: enable/disable right mouse button -:track: enable/disable moving cursor in build and designation mode -:edge: enable/disable active edge scrolling (when on, will also enable tracking) -:live: enable/disable query view when unpaused -:delay: Set delay when edge scrolling in tracking mode. Omit amount to display current setting. - -Usage:: - - mousequery [plugin] [rbutton] [track] [edge] [live] [enable|disable] - -.. _nopause: - -nopause -======= -Disables pausing (both manual and automatic) with the exception of pause forced -by `reveal` ``hell``. This is nice for digging under rivers. - -.. _rename: - -rename -====== -Allows renaming various things. Use `gui/rename` for an in-game interface. - -Options: - -``rename squad "name"`` - Rename squad by index to 'name'. -``rename hotkey \"name\"`` - Rename hotkey by index. This allows assigning - longer commands to the DF hotkeys. -``rename unit "nickname"`` - Rename a unit/creature highlighted in the DF user interface. -``rename unit-profession "custom profession"`` - Change proffession name of the highlighted unit/creature. -``rename building "name"`` - Set a custom name for the selected building. - The building must be one of stockpile, workshop, furnace, trap, - siege engine or an activity zone. - -.. _rendermax: - -rendermax -========= -A collection of renderer replacing/enhancing filters. For better effect try changing the -black color in palette to non totally black. See :forums:`128487` for more info. - -Options: - -:trippy: Randomizes the color of each tiles. Used for fun, or testing. -:light: Enable lighting engine. -:light reload: Reload the settings file. -:light sun |cycle: Set time to (in hours) or set it to df time cycle. -:occlusionON, occlusionOFF: Show debug occlusion info. -:disable: Disable any filter that is enabled. - -An image showing lava and dragon breath. Not pictured here: sunlight, shining items/plants, -materials that color the light etc... - -.. image:: images/rendermax.png - -.. _resume: - -resume -====== -Allows automatic resumption of suspended constructions, along with colored -UI hints for construction status. - -.. _rb: -.. _ruby: - -ruby -==== -Ruby language plugin, which evaluates the following arguments as a ruby string. -Best used as ``:rb [string]``, for the special parsing mode. Alias ``rb_eval``. - -.. comment - the link target "search" is reserved for the Sphinx search page -.. _search-plugin: - -search -====== -The search plugin adds search to the Stocks, Animals, Trading, Stockpile, -Noble (assignment candidates), Military (position candidates), Burrows -(unit list), Rooms, Announcements, Job List and Unit List screens. - -.. image:: images/search.png - -Searching works the same way as the search option in :guilabel:`Move to Depot`. -You will see the Search option displayed on screen with a hotkey (usually :kbd:`s`). -Pressing it lets you start typing a query and the relevant list will start -filtering automatically. - -Pressing :kbd:`Enter`, :kbd:`Esc` or the arrow keys will return you to browsing the now -filtered list, which still functions as normal. You can clear the filter -by either going back into search mode and backspacing to delete it, or -pressing the "shifted" version of the search hotkey while browsing the -list (e.g. if the hotkey is :kbd:`s`, then hitting :kbd:`Shift`:kbd:`s` will clear any -filter). - -Leaving any screen automatically clears the filter. - -In the Trade screen, the actual trade will always only act on items that -are actually visible in the list; the same effect applies to the Trade -Value numbers displayed by the screen. Because of this, the :kbd:`t` key is -blocked while search is active, so you have to reset the filters first. -Pressing :kbd:`Alt`:kbd:`C` will clear both search strings. - -In the stockpile screen the option only appears if the cursor is in the -rightmost list: - -.. image:: images/search-stockpile.png - -Note that the 'Permit XXX'/'Forbid XXX' keys conveniently operate only -on items actually shown in the rightmost list, so it is possible to select -only fat or tallow by forbidding fats, then searching for fat/tallow, and -using Permit Fats again while the list is filtered. - -.. _sort: -.. _sort-items: - -sort-items -========== -Sort the visible item list:: - - sort-items order [order...] - -Sort the item list using the given sequence of comparisons. -The ``<`` prefix for an order makes undefined values sort first. -The ``>`` prefix reverses the sort order for defined values. - -Item order examples:: - - description material wear type quality - -The orderings are defined in ``hack/lua/plugins/sort/*.lua`` - -.. _sort-units: - -sort-units -========== -Sort the visible unit list:: - - sort-units order [order...] - -Sort the unit list using the given sequence of comparisons. -The ``<`` prefix for an order makes undefined values sort first. -The ``>`` prefix reverses the sort order for defined values. - -Unit order examples:: - - name age arrival squad squad_position profession - -The orderings are defined in ``hack/lua/plugins/sort/*.lua`` - -:dfhack-keybind:`sort-units` - -.. _stocksettings: -.. _stockpiles: - -stockpiles -========== -Offers the following commands to save and load stockpile settings. -See `gui/stockpiles` for an in-game interface. - -:copystock: Copies the parameters of the currently highlighted stockpile to the custom - stockpile settings and switches to custom stockpile placement mode, effectively - allowing you to copy/paste stockpiles easily. - :dfhack-keybind:`copystock` - -:savestock: Saves the currently highlighted stockpile's settings to a file in your Dwarf - Fortress folder. This file can be used to copy settings between game saves or - players. e.g.: ``savestock food_settings.dfstock`` - -:loadstock: Loads a saved stockpile settings file and applies it to the currently selected - stockpile. e.g.: ``loadstock food_settings.dfstock`` - -To use savestock and loadstock, use the :kbd:`q` command to highlight a stockpile. -Then run savestock giving it a descriptive filename. Then, in a different (or -the same!) gameworld, you can highlight any stockpile with :kbd:`q` then execute the -``loadstock`` command passing it the name of that file. The settings will be -applied to that stockpile. - -Note that files are relative to the DF folder, so put your files there or in a -subfolder for easy access. Filenames should not have spaces. Generated materials, -divine metals, etc are not saved as they are different in every world. - -.. _stocks: - -stocks -====== -Replaces the DF stocks screen with an improved version. - -:dfhack-keybind:`stocks` - - -.. _title-folder: - -title-folder -============= -Displays the DF folder name in the window title bar when enabled. - -.. _title-version: - -title-version -============= -Displays the DFHack version on DF's title screen when enabled. - -.. _trackstop: - -trackstop -========= -Adds a :kbd:`q` menu for track stops, which is completely blank by default. -This allows you to view and/or change the track stop's friction and dump -direction settings, using the keybindings from the track stop building interface. diff --git a/docs/Removed.rst b/docs/Removed.rst index e94b04cf8..3dbe26820 100644 --- a/docs/Removed.rst +++ b/docs/Removed.rst @@ -59,6 +59,14 @@ script instead. You can use your existing .csv files. Just move them to the ``blueprints`` folder in your DF installation, and instead of ``fortplan file.csv`` run ``quickfort run file.csv``. +.. _gui/no-dfhack-init: + +gui/no-dfhack-init +================== +Tool that warned the user when the ``dfhack.init`` file did not exist. Now that +``dfhack.init`` is autogenerated in ``dfhack-config/init``, this warning is no +longer necessary. + .. _warn-stuck-trees: warn-stuck-trees diff --git a/docs/Scripts.rst b/docs/Scripts.rst deleted file mode 100644 index 419385582..000000000 --- a/docs/Scripts.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. _scripts-index: - -############## -DFHack Scripts -############## - -Lua or ruby scripts placed in the :file:`hack/scripts/` directory -are considered for execution as if they were native DFHack commands. - -The following pages document all the scripts in the DFHack standard library. - -.. toctree:: - :maxdepth: 2 - - /docs/_auto/base - /docs/_auto/devel - /docs/_auto/fix - /docs/_auto/gui - /docs/_auto/modtools diff --git a/docs/Tools.rst b/docs/Tools.rst deleted file mode 100644 index 15c5f878e..000000000 --- a/docs/Tools.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. _tools-index: - -############ -DFHack Tools -############ - -These are the DFHack commands you can run. - -.. toctree:: - :titlesonly: - :glob: - - /docs/tools/* diff --git a/docs/_auto/.gitignore b/docs/_auto/.gitignore deleted file mode 100644 index 30d85567b..000000000 --- a/docs/_auto/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.rst diff --git a/docs/build-pdf.sh b/docs/build-pdf.sh index 76908b49b..735ef2faa 100755 --- a/docs/build-pdf.sh +++ b/docs/build-pdf.sh @@ -11,13 +11,4 @@ cd $(dirname "$0") cd .. -sphinx=sphinx-build -if [ -n "$SPHINX" ]; then - sphinx=$SPHINX -fi - -if [ -z "$JOBS" ]; then - JOBS=2 -fi - -"$sphinx" -M latexpdf . ./docs/pdf -w ./docs/_sphinx-warnings.txt -j "$JOBS" "$@" +"${SPHINX:-sphinx-build}" -M latexpdf -d build/docs/pdf . docs/pdf -w build/docs/pdf/_sphinx-warnings.txt -j "${JOBS:-auto}" "$@" diff --git a/docs/build.sh b/docs/build.sh index 95a97e539..c696d5fbe 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -11,13 +11,5 @@ cd $(dirname "$0") cd .. -sphinx=sphinx-build -if [ -n "$SPHINX" ]; then - sphinx=$SPHINX -fi - -if [ -z "$JOBS" ]; then - JOBS=2 -fi - -"$sphinx" -a -b html . ./docs/html -w ./docs/_sphinx-warnings.txt -j "$JOBS" "$@" +"${SPHINX:-sphinx-build}" -b html -d build/docs/html . docs/html -w build/docs/html/_sphinx-warnings.txt -j "${JOBS:-auto}" "$@" +"${SPHINX:-sphinx-build}" -b text -d build/docs/text . docs/text -w build/docs/text/_sphinx-warnings.txt -j "${JOBS:-auto}" "$@" diff --git a/docs/_changelogs/.gitignore b/docs/changelogs/.gitignore similarity index 50% rename from docs/_changelogs/.gitignore rename to docs/changelogs/.gitignore index 2211df63d..90de5c70d 100644 --- a/docs/_changelogs/.gitignore +++ b/docs/changelogs/.gitignore @@ -1 +1,2 @@ *.txt +*.rst diff --git a/docs/guides/examples-guide.rst b/docs/guides/examples-guide.rst index b699b8204..f61ada4d7 100644 --- a/docs/guides/examples-guide.rst +++ b/docs/guides/examples-guide.rst @@ -28,8 +28,8 @@ it is useful (and customizable) for any fort. It includes the following config: - Calls `ban-cooking` for items that have important alternate uses and should not be cooked. This configuration is only set when a fortress is first started, so later manual changes will not be overridden. -- Automates calling of various fort maintenance and `scripts-fix`, like - `cleanowned` and `fix/stuckdoors`. +- Automates calling of various fort maintenance scripts, like `cleanowned` and + `fix/stuckdoors`. - Keeps your manager orders intelligently ordered with `orders` ``sort`` so no orders block other orders from ever getting completed. - Periodically enqueues orders to shear and milk shearable and milkable pets. diff --git a/docs/index-tools.rst b/docs/index-tools.rst new file mode 100644 index 000000000..dca14ae3b --- /dev/null +++ b/docs/index-tools.rst @@ -0,0 +1,24 @@ +.. _tools-index: + +============ +DFHack Tools +============ + +These pages contain information about the plugins, scripts, and built-in +commands distributed with DFHack. + +.. note:: + In order to avoid user confusion, as a matter of policy all GUI tools + display the word :guilabel:`DFHack` on the screen somewhere while active. + + When that is not appropriate because they merely add keybinding hints to + existing DF screens, they deliberately use red instead of green for the key. + +.. toctree:: + :titlesonly: + :glob: + + /docs/Tags + /docs/Builtin + /docs/tools/* + /docs/tools/*/* diff --git a/docs/tools/cromulate.rst b/docs/plugins/cromulate.rst similarity index 100% rename from docs/tools/cromulate.rst rename to docs/plugins/cromulate.rst diff --git a/docs/sphinx_extensions/dfhack/changelog.py b/docs/sphinx_extensions/dfhack/changelog.py index f7405d8b1..629d6b6de 100644 --- a/docs/sphinx_extensions/dfhack/changelog.py +++ b/docs/sphinx_extensions/dfhack/changelog.py @@ -238,8 +238,8 @@ def generate_changelog(all=False): consolidate_changelog(stable_entries) consolidate_changelog(dev_entries) - print_changelog(versions, stable_entries, os.path.join(DOCS_ROOT, '_auto/news.rst')) - print_changelog(versions, dev_entries, os.path.join(DOCS_ROOT, '_auto/news-dev.rst')) + print_changelog(versions, stable_entries, os.path.join(DOCS_ROOT, 'changelogs/news.rst')) + print_changelog(versions, dev_entries, os.path.join(DOCS_ROOT, 'changelogs/news-dev.rst')) if all: for version in versions: @@ -251,10 +251,10 @@ def generate_changelog(all=False): else: version_entries = {version: dev_entries[version]} print_changelog([version], version_entries, - os.path.join(DOCS_ROOT, '_changelogs/%s-github.txt' % version), + os.path.join(DOCS_ROOT, 'changelogs/%s-github.txt' % version), replace=False) print_changelog([version], version_entries, - os.path.join(DOCS_ROOT, '_changelogs/%s-reddit.txt' % version), + os.path.join(DOCS_ROOT, 'changelogs/%s-reddit.txt' % version), replace=False, prefix='> ') @@ -264,7 +264,7 @@ def cli_entrypoint(): import argparse parser = argparse.ArgumentParser() parser.add_argument('-a', '--all', action='store_true', - help='Print changelogs for all versions to docs/_changelogs') + help='Print changelogs for all versions to docs/changelogs') parser.add_argument('-c', '--check', action='store_true', help='Check that all entries are printed') args = parser.parse_args() @@ -272,9 +272,9 @@ def cli_entrypoint(): entries = generate_changelog(all=args.all) if args.check: - with open(os.path.join(DOCS_ROOT, '_auto/news.rst')) as f: + with open(os.path.join(DOCS_ROOT, 'changelogs/news.rst')) as f: content_stable = f.read() - with open(os.path.join(DOCS_ROOT, '_auto/news-dev.rst')) as f: + with open(os.path.join(DOCS_ROOT, 'changelogs/news-dev.rst')) as f: content_dev = f.read() for entry in entries: for description in entry.children: diff --git a/index.rst b/index.rst index 3b46699a7..29ac3c129 100644 --- a/index.rst +++ b/index.rst @@ -30,11 +30,7 @@ User Manual /docs/Installing /docs/Support /docs/Core - /docs/Builtin - /docs/Plugins - /docs/Scripts - /docs/Tags - /docs/Tools + /docs/index-tools /docs/guides/index /docs/index-about /docs/index-dev From 3e2320aa607154642cbefad116f92a5ef35c4974 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 9 Jul 2022 23:01:46 -0700 Subject: [PATCH 185/854] split the plugin docs into individual files --- docs/plugins/3dveins.rst | 14 ++ docs/plugins/add-spatter.rst | 6 + docs/plugins/adv-bodyswap.rst | 12 ++ docs/plugins/alltraffic.rst | 11 ++ docs/plugins/autobutcher.rst | 91 ++++++++++ docs/plugins/autochop.rst | 16 ++ docs/plugins/autoclothing.rst | 14 ++ docs/plugins/autodump.rst | 29 ++++ docs/plugins/autofarm.rst | 21 +++ docs/plugins/autogems.rst | 7 + docs/plugins/autohauler.rst | 20 +++ docs/plugins/autolabor.rst | 70 ++++++++ docs/plugins/automaterial.rst | 33 ++++ docs/plugins/automelt.rst | 5 + docs/plugins/autonestbox.rst | 19 ++ docs/plugins/autotrade.rst | 5 + docs/plugins/blueprint.rst | 112 ++++++++++++ docs/plugins/buildingplan.rst | 88 ++++++++++ docs/plugins/burrows.rst | 38 ++++ docs/plugins/changeitem.rst | 26 +++ docs/plugins/changelayer.rst | 60 +++++++ docs/plugins/changevein.rst | 10 ++ docs/plugins/clean.rst | 16 ++ docs/plugins/cleanconst.rst | 7 + docs/plugins/cleanowned.rst | 19 ++ docs/plugins/command-prompt.rst | 16 ++ docs/plugins/confirm.rst | 12 ++ docs/plugins/createitem.rst | 48 ++++++ docs/plugins/cursecheck.rst | 42 +++++ docs/plugins/debug.rst | 89 ++++++++++ docs/plugins/deramp.rst | 6 + docs/plugins/dig-now.rst | 61 +++++++ docs/plugins/dig.rst | 20 +++ docs/plugins/digFlood.rst | 18 ++ docs/plugins/digcircle.rst | 35 ++++ docs/plugins/digexp.rst | 31 ++++ docs/plugins/diggingInvaders.rst | 29 ++++ docs/plugins/digl.rst | 20 +++ docs/plugins/diglx.rst | 20 +++ docs/plugins/digtype.rst | 19 ++ docs/plugins/digv.rst | 20 +++ docs/plugins/digvx.rst | 20 +++ docs/plugins/dwarfmonitor.rst | 78 +++++++++ docs/plugins/dwarfvet.rst | 19 ++ docs/plugins/embark-assistant.rst | 9 + docs/plugins/embark-tools.rst | 12 ++ docs/plugins/fastdwarf.rst | 14 ++ docs/plugins/filltraffic.rst | 17 ++ docs/plugins/fix-armory.rst | 4 + docs/plugins/fix-job-postings.rst | 5 + docs/plugins/fix-unit-occupancy.rst | 12 ++ docs/plugins/fixveins.rst | 5 + docs/plugins/flows.rst | 5 + docs/plugins/follow.rst | 5 + docs/plugins/forceequip.rst | 7 + docs/plugins/generated-creature-renamer.rst | 15 ++ docs/plugins/getplants.rst | 37 ++++ docs/plugins/hotkeys.rst | 8 + docs/plugins/infiniteSky.rst | 16 ++ docs/plugins/isoworldremote.rst | 3 + docs/plugins/job-duplicate.rst | 6 + docs/plugins/job-material.rst | 15 ++ docs/plugins/job.rst | 15 ++ docs/plugins/labormanager.rst | 105 +++++++++++ docs/plugins/lair.rst | 12 ++ docs/plugins/liquids-here.rst | 6 + docs/plugins/liquids.rst | 65 +++++++ docs/plugins/manipulator.rst | 182 ++++++++++++++++++++ docs/plugins/misery.rst | 14 ++ docs/plugins/mode.rst | 21 +++ docs/plugins/mousequery.rst | 16 ++ docs/plugins/nestboxes.rst | 5 + docs/plugins/nopause.rst | 4 + docs/plugins/orders.rst | 116 +++++++++++++ docs/plugins/petcapRemover.rst | 19 ++ docs/plugins/plant.rst | 16 ++ docs/plugins/power-meter.rst | 6 + docs/plugins/probe.rst | 13 ++ docs/plugins/prospect.rst | 51 ++++++ docs/plugins/prospector.rst | 51 ++++++ docs/plugins/rb.rst | 4 + docs/plugins/regrass.rst | 3 + docs/plugins/remotefortressreader.rst | 4 + docs/plugins/rename.rst | 19 ++ docs/plugins/rendermax.rst | 18 ++ docs/plugins/restrictice.rst | 4 + docs/plugins/restrictliquids.rst | 4 + docs/plugins/resume.rst | 4 + docs/plugins/reveal.rst | 23 +++ docs/plugins/revflood.rst | 23 +++ docs/plugins/revforget.rst | 23 +++ docs/plugins/revtoggle.rst | 23 +++ docs/plugins/ruby.rst | 4 + docs/plugins/search.rst | 40 +++++ docs/plugins/seedwatch.rst | 27 +++ docs/plugins/showmood.rst | 3 + docs/plugins/siege-engine.rst | 12 ++ docs/plugins/sort-items.rst | 17 ++ docs/plugins/sort-units.rst | 17 ++ docs/plugins/spectate.rst | 5 + docs/plugins/spotclean.rst | 6 + docs/plugins/steam-engine.rst | 84 +++++++++ docs/plugins/stockflow.rst | 31 ++++ docs/plugins/stockpiles.rst | 28 +++ docs/plugins/stocks.rst | 6 + docs/plugins/stonesense.rst | 10 ++ docs/plugins/strangemood.rst | 19 ++ docs/plugins/tailor.rst | 11 ++ docs/plugins/tiletypes-command.rst | 10 ++ docs/plugins/tiletypes-here-point.rst | 6 + docs/plugins/tiletypes-here.rst | 14 ++ docs/plugins/tiletypes.rst | 82 +++++++++ docs/plugins/title-folder.rst | 3 + docs/plugins/title-version.rst | 3 + docs/plugins/trackstop.rst | 5 + docs/plugins/tubefill.rst | 11 ++ docs/plugins/tweak.rst | 91 ++++++++++ docs/plugins/unreveal.rst | 23 +++ docs/plugins/workNow.rst | 12 ++ docs/plugins/workflow.rst | 113 ++++++++++++ docs/plugins/zone.rst | 130 ++++++++++++++ 121 files changed, 3239 insertions(+) create mode 100644 docs/plugins/3dveins.rst create mode 100644 docs/plugins/add-spatter.rst create mode 100644 docs/plugins/adv-bodyswap.rst create mode 100644 docs/plugins/alltraffic.rst create mode 100644 docs/plugins/autobutcher.rst create mode 100644 docs/plugins/autochop.rst create mode 100644 docs/plugins/autoclothing.rst create mode 100644 docs/plugins/autodump.rst create mode 100644 docs/plugins/autofarm.rst create mode 100644 docs/plugins/autogems.rst create mode 100644 docs/plugins/autohauler.rst create mode 100644 docs/plugins/autolabor.rst create mode 100644 docs/plugins/automaterial.rst create mode 100644 docs/plugins/automelt.rst create mode 100644 docs/plugins/autonestbox.rst create mode 100644 docs/plugins/autotrade.rst create mode 100644 docs/plugins/blueprint.rst create mode 100644 docs/plugins/buildingplan.rst create mode 100644 docs/plugins/burrows.rst create mode 100644 docs/plugins/changeitem.rst create mode 100644 docs/plugins/changelayer.rst create mode 100644 docs/plugins/changevein.rst create mode 100644 docs/plugins/clean.rst create mode 100644 docs/plugins/cleanconst.rst create mode 100644 docs/plugins/cleanowned.rst create mode 100644 docs/plugins/command-prompt.rst create mode 100644 docs/plugins/confirm.rst create mode 100644 docs/plugins/createitem.rst create mode 100644 docs/plugins/cursecheck.rst create mode 100644 docs/plugins/debug.rst create mode 100644 docs/plugins/deramp.rst create mode 100644 docs/plugins/dig-now.rst create mode 100644 docs/plugins/dig.rst create mode 100644 docs/plugins/digFlood.rst create mode 100644 docs/plugins/digcircle.rst create mode 100644 docs/plugins/digexp.rst create mode 100644 docs/plugins/diggingInvaders.rst create mode 100644 docs/plugins/digl.rst create mode 100644 docs/plugins/diglx.rst create mode 100644 docs/plugins/digtype.rst create mode 100644 docs/plugins/digv.rst create mode 100644 docs/plugins/digvx.rst create mode 100644 docs/plugins/dwarfmonitor.rst create mode 100644 docs/plugins/dwarfvet.rst create mode 100644 docs/plugins/embark-assistant.rst create mode 100644 docs/plugins/embark-tools.rst create mode 100644 docs/plugins/fastdwarf.rst create mode 100644 docs/plugins/filltraffic.rst create mode 100644 docs/plugins/fix-armory.rst create mode 100644 docs/plugins/fix-job-postings.rst create mode 100644 docs/plugins/fix-unit-occupancy.rst create mode 100644 docs/plugins/fixveins.rst create mode 100644 docs/plugins/flows.rst create mode 100644 docs/plugins/follow.rst create mode 100644 docs/plugins/forceequip.rst create mode 100644 docs/plugins/generated-creature-renamer.rst create mode 100644 docs/plugins/getplants.rst create mode 100644 docs/plugins/hotkeys.rst create mode 100644 docs/plugins/infiniteSky.rst create mode 100644 docs/plugins/isoworldremote.rst create mode 100644 docs/plugins/job-duplicate.rst create mode 100644 docs/plugins/job-material.rst create mode 100644 docs/plugins/job.rst create mode 100644 docs/plugins/labormanager.rst create mode 100644 docs/plugins/lair.rst create mode 100644 docs/plugins/liquids-here.rst create mode 100644 docs/plugins/liquids.rst create mode 100644 docs/plugins/manipulator.rst create mode 100644 docs/plugins/misery.rst create mode 100644 docs/plugins/mode.rst create mode 100644 docs/plugins/mousequery.rst create mode 100644 docs/plugins/nestboxes.rst create mode 100644 docs/plugins/nopause.rst create mode 100644 docs/plugins/orders.rst create mode 100644 docs/plugins/petcapRemover.rst create mode 100644 docs/plugins/plant.rst create mode 100644 docs/plugins/power-meter.rst create mode 100644 docs/plugins/probe.rst create mode 100644 docs/plugins/prospect.rst create mode 100644 docs/plugins/prospector.rst create mode 100644 docs/plugins/rb.rst create mode 100644 docs/plugins/regrass.rst create mode 100644 docs/plugins/remotefortressreader.rst create mode 100644 docs/plugins/rename.rst create mode 100644 docs/plugins/rendermax.rst create mode 100644 docs/plugins/restrictice.rst create mode 100644 docs/plugins/restrictliquids.rst create mode 100644 docs/plugins/resume.rst create mode 100644 docs/plugins/reveal.rst create mode 100644 docs/plugins/revflood.rst create mode 100644 docs/plugins/revforget.rst create mode 100644 docs/plugins/revtoggle.rst create mode 100644 docs/plugins/ruby.rst create mode 100644 docs/plugins/search.rst create mode 100644 docs/plugins/seedwatch.rst create mode 100644 docs/plugins/showmood.rst create mode 100644 docs/plugins/siege-engine.rst create mode 100644 docs/plugins/sort-items.rst create mode 100644 docs/plugins/sort-units.rst create mode 100644 docs/plugins/spectate.rst create mode 100644 docs/plugins/spotclean.rst create mode 100644 docs/plugins/steam-engine.rst create mode 100644 docs/plugins/stockflow.rst create mode 100644 docs/plugins/stockpiles.rst create mode 100644 docs/plugins/stocks.rst create mode 100644 docs/plugins/stonesense.rst create mode 100644 docs/plugins/strangemood.rst create mode 100644 docs/plugins/tailor.rst create mode 100644 docs/plugins/tiletypes-command.rst create mode 100644 docs/plugins/tiletypes-here-point.rst create mode 100644 docs/plugins/tiletypes-here.rst create mode 100644 docs/plugins/tiletypes.rst create mode 100644 docs/plugins/title-folder.rst create mode 100644 docs/plugins/title-version.rst create mode 100644 docs/plugins/trackstop.rst create mode 100644 docs/plugins/tubefill.rst create mode 100644 docs/plugins/tweak.rst create mode 100644 docs/plugins/unreveal.rst create mode 100644 docs/plugins/workNow.rst create mode 100644 docs/plugins/workflow.rst create mode 100644 docs/plugins/zone.rst diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst new file mode 100644 index 000000000..0e6841792 --- /dev/null +++ b/docs/plugins/3dveins.rst @@ -0,0 +1,14 @@ +3dveins +======= +Removes all existing veins from the map and generates new ones using +3D Perlin noise, in order to produce a layout that smoothly flows between +Z levels. The vein distribution is based on the world seed, so running +the command for the second time should produce no change. It is best to +run it just once immediately after embark. + +This command is intended as only a cosmetic change, so it takes +care to exactly preserve the mineral counts reported by `prospect` ``all``. +The amounts of different layer stones may slightly change in some cases +if vein mass shifts between Z layers. + +The only undo option is to restore your save from backup. diff --git a/docs/plugins/add-spatter.rst b/docs/plugins/add-spatter.rst new file mode 100644 index 000000000..15b99ff52 --- /dev/null +++ b/docs/plugins/add-spatter.rst @@ -0,0 +1,6 @@ +add-spatter +=========== +This plugin makes reactions with names starting with ``SPATTER_ADD_`` +produce contaminants on the items instead of improvements. The plugin is +intended to give some use to all those poisons that can be bought from caravans, +so they're immune to being washed away by water or destroyed by `clean`. diff --git a/docs/plugins/adv-bodyswap.rst b/docs/plugins/adv-bodyswap.rst new file mode 100644 index 000000000..46b961517 --- /dev/null +++ b/docs/plugins/adv-bodyswap.rst @@ -0,0 +1,12 @@ +adv-bodyswap +============ +This allows taking control over your followers and other creatures in adventure +mode. For example, you can make them pick up new arms and armor and equip them +properly. + +Usage: + +* When viewing unit details, body-swaps into that unit. +* In the main adventure mode screen, reverts transient swap. + +:dfhack-keybind:`adv-bodyswap` diff --git a/docs/plugins/alltraffic.rst b/docs/plugins/alltraffic.rst new file mode 100644 index 000000000..1e9116954 --- /dev/null +++ b/docs/plugins/alltraffic.rst @@ -0,0 +1,11 @@ +alltraffic +========== +Set traffic designations for every single tile of the map - useful for resetting +traffic designations. See also `filltraffic`, `restrictice`, and `restrictliquids`. + +Options: + +:H: High Traffic +:N: Normal Traffic +:L: Low Traffic +:R: Restricted Traffic diff --git a/docs/plugins/autobutcher.rst b/docs/plugins/autobutcher.rst new file mode 100644 index 000000000..d761cbdbe --- /dev/null +++ b/docs/plugins/autobutcher.rst @@ -0,0 +1,91 @@ +autobutcher +=========== +Assigns lifestock for slaughter once it reaches a specific count. Requires that +you add the target race(s) to a watch list. Only tame units will be processed. + +Units will be ignored if they are: + +* Nicknamed (for custom protection; you can use the `rename` ``unit`` tool + individually, or `zone` ``nick`` for groups) +* Caged, if and only if the cage is defined as a room (to protect zoos) +* Trained for war or hunting + +Creatures who will not reproduce (because they're not interested in the +opposite sex or have been gelded) will be butchered before those who will. +Older adults and younger children will be butchered first if the population +is above the target (default 1 male, 5 female kids and adults). Note that +you may need to set a target above 1 to have a reliable breeding population +due to asexuality etc. See `fix-ster` if this is a problem. + +Options: + +:example: Print some usage examples. +:start: Start running every X frames (df simulation ticks). + Default: X=6000, which would be every 60 seconds at 100fps. +:stop: Stop running automatically. +:sleep : Changes the timer to sleep X frames between runs. +:watch R: Start watching a race. R can be a valid race RAW id (ALPACA, + BIRD_TURKEY, etc) or a list of ids seperated by spaces or + the keyword 'all' which affects all races on your current + watchlist. +:unwatch R: Stop watching race(s). The current target settings will be + remembered. R can be a list of ids or the keyword 'all'. +:forget R: Stop watching race(s) and forget it's/their target settings. + R can be a list of ids or the keyword 'all'. +:autowatch: Automatically adds all new races (animals you buy from merchants, + tame yourself or get from migrants) to the watch list using + default target count. +:noautowatch: Stop auto-adding new races to the watchlist. +:list: Print the current status and watchlist. +:list_export: Print the commands needed to set up status and watchlist, + which can be used to import them to another save (see notes). +:target : + Set target count for specified race(s). The first four arguments + are the number of female and male kids, and female and male adults. + R can be a list of spceies ids, or the keyword ``all`` or ``new``. + ``R = 'all'``: change target count for all races on watchlist + and set the new default for the future. ``R = 'new'``: don't touch + current settings on the watchlist, only set the new default + for future entries. +:list_export: Print the commands required to rebuild your current settings. + +.. note:: + + Settings and watchlist are stored in the savegame, so that you can have + different settings for each save. If you want to copy your watchlist to + another savegame you must export the commands required to recreate your settings. + + To export, open an external terminal in the DF directory, and run + ``dfhack-run autobutcher list_export > filename.txt``. To import, load your + new save and run ``script filename.txt`` in the DFHack terminal. + + +Examples: + +You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, +1 male) of the race alpaca. Once the kids grow up the oldest adults will get +slaughtered. Excess kids will get slaughtered starting with the youngest +to allow that the older ones grow into adults. Any unnamed cats will +be slaughtered as soon as possible. :: + + autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY + autobutcher target 0 0 0 0 CAT + autobutcher watch ALPACA BIRD_TURKEY CAT + autobutcher start + +Automatically put all new races onto the watchlist and mark unnamed tame units +for slaughter as soon as they arrive in your fort. Settings already made +for specific races will be left untouched. :: + + autobutcher target 0 0 0 0 new + autobutcher autowatch + autobutcher start + +Stop watching the races alpaca and cat, but remember the target count +settings so that you can use 'unwatch' without the need to enter the +values again. Note: 'autobutcher unwatch all' works, but only makes sense +if you want to keep the plugin running with the 'autowatch' feature or manually +add some new races with 'watch'. If you simply want to stop it completely use +'autobutcher stop' instead. :: + + autobutcher unwatch ALPACA CAT diff --git a/docs/plugins/autochop.rst b/docs/plugins/autochop.rst new file mode 100644 index 000000000..4a669480f --- /dev/null +++ b/docs/plugins/autochop.rst @@ -0,0 +1,16 @@ +autochop +======== +Automatically manage tree cutting designation to keep available logs withing given +quotas. + +Open the dashboard by running:: + + enable autochop + +The plugin must be activated (with :kbd:`d`-:kbd:`t`-:kbd:`c`-:kbd:`a`) before +it can be used. You can then set logging quotas and restrict designations to +specific burrows (with 'Enter') if desired. The plugin's activity cycle runs +once every in game day. + +If you add ``enable autochop`` to your dfhack.init there will be a hotkey to +open the dashboard from the chop designation menu. diff --git a/docs/plugins/autoclothing.rst b/docs/plugins/autoclothing.rst new file mode 100644 index 000000000..7169844dd --- /dev/null +++ b/docs/plugins/autoclothing.rst @@ -0,0 +1,14 @@ +autoclothing +============ + +Automatically manage clothing work orders, allowing the user to set how many of +each clothing type every citizen should have. Usage:: + + autoclothing [number] + +Examples: + +* ``autoclothing cloth "short skirt" 10``: + Sets the desired number of cloth short skirts available per citizen to 10. +* ``autoclothing cloth dress``: + Displays the currently set number of cloth dresses chosen per citizen. diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst new file mode 100644 index 000000000..9bd74cff7 --- /dev/null +++ b/docs/plugins/autodump.rst @@ -0,0 +1,29 @@ +autodump +======== +This plugin adds an option to the :kbd:`q` menu for stckpiles when `enabled `. +When autodump is enabled for a stockpile, any items placed in the stockpile will +automatically be designated to be dumped. + +Alternatively, you can use it to quickly move all items designated to be dumped. +Items are instantly moved to the cursor position, the dump flag is unset, +and the forbid flag is set, as if it had been dumped normally. +Be aware that any active dump item tasks still point at the item. + +Cursor must be placed on a floor tile so the items can be dumped there. + +Options: + +:destroy: Destroy instead of dumping. Doesn't require a cursor. + If called again before the game is resumed, cancels destroy. +:destroy-here: As ``destroy``, but only the selected item in the :kbd:`k` list, + or inside a container. + Alias ``autodump-destroy-here``, for keybindings. + :dfhack-keybind:`autodump-destroy-here` +:visible: Only process items that are not hidden. +:hidden: Only process hidden items. +:forbidden: Only process forbidden items (default: only unforbidden). + +``autodump-destroy-item`` destroys the selected item, which may be selected +in the :kbd:`k` list, or inside a container. If called again before the game +is resumed, cancels destruction of the item. +:dfhack-keybind:`autodump-destroy-item` diff --git a/docs/plugins/autofarm.rst b/docs/plugins/autofarm.rst new file mode 100644 index 000000000..b330517d0 --- /dev/null +++ b/docs/plugins/autofarm.rst @@ -0,0 +1,21 @@ +autofarm +======== + +Automatically handles crop selection in farm plots based on current plant +stocks, and selects crops for planting if current stock is below a threshold. +Selected crops are dispatched on all farmplots. (Note that this plugin replaces +an older Ruby script of the same name.) + +Use the `enable` or `disable ` commands to change whether this plugin is +enabled. + +Usage: + +* ``autofarm runonce``: + Updates all farm plots once, without enabling the plugin +* ``autofarm status``: + Prints status information, including any applied limits +* ``autofarm default 30``: + Sets the default threshold +* ``autofarm threshold 150 helmet_plump tail_pig``: + Sets thresholds of individual plants diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst new file mode 100644 index 000000000..59800e1b9 --- /dev/null +++ b/docs/plugins/autogems.rst @@ -0,0 +1,7 @@ +autogems +======== +Creates a new Workshop Order setting, automatically cutting rough gems +when `enabled `. + +See `gui/autogems` for a configuration UI. If necessary, the ``autogems-reload`` +command reloads the configuration file produced by that script. diff --git a/docs/plugins/autohauler.rst b/docs/plugins/autohauler.rst new file mode 100644 index 000000000..289874f94 --- /dev/null +++ b/docs/plugins/autohauler.rst @@ -0,0 +1,20 @@ +autohauler +========== +Autohauler is an autolabor fork. + +Rather than the all-of-the-above means of autolabor, autohauler will instead +only manage hauling labors and leave skilled labors entirely to the user, who +will probably use Dwarf Therapist to do so. + +Idle dwarves will be assigned the hauling labors; everyone else (including +those currently hauling) will have the hauling labors removed. This is to +encourage every dwarf to do their assigned skilled labors whenever possible, +but resort to hauling when those jobs are not available. This also implies +that the user will have a very tight skill assignment, with most skilled +labors only being assigned to just one dwarf, no dwarf having more than two +active skilled labors, and almost every non-military dwarf having at least +one skilled labor assigned. + +Autohauler allows skills to be flagged as to prevent hauling labors from +being assigned when the skill is present. By default this is the unused +ALCHEMIST labor but can be changed by the user. diff --git a/docs/plugins/autolabor.rst b/docs/plugins/autolabor.rst new file mode 100644 index 000000000..b63b9102b --- /dev/null +++ b/docs/plugins/autolabor.rst @@ -0,0 +1,70 @@ +autolabor +========= +Automatically manage dwarf labors to efficiently complete jobs. +Autolabor tries to keep as many dwarves as possible busy but +also tries to have dwarves specialize in specific skills. + +The key is that, for almost all labors, once a dwarf begins a job it will finish that +job even if the associated labor is removed. Autolabor therefore frequently checks +which dwarf or dwarves should take new jobs for that labor, and sets labors accordingly. +Labors with equipment (mining, hunting, and woodcutting), which are abandoned +if labors change mid-job, are handled slightly differently to minimise churn. + +.. warning:: + + *autolabor will override any manual changes you make to labors while + it is enabled, including through other tools such as Dwarf Therapist* + +Simple usage: + +:enable autolabor: Enables the plugin with default settings. (Persistent per fortress) +:disable autolabor: Disables the plugin. + +Anything beyond this is optional - autolabor works well on the default settings. + +By default, each labor is assigned to between 1 and 200 dwarves (2-200 for mining). +By default 33% of the workforce become haulers, who handle all hauling jobs as well +as cleaning, pulling levers, recovering wounded, removing constructions, and filling ponds. +Other jobs are automatically assigned as described above. Each of these settings can be adjusted. + +Jobs are rarely assigned to nobles with responsibilities for meeting diplomats or merchants, +never to the chief medical dwarf, and less often to the bookeeper and manager. + +Hunting is never assigned without a butchery, and fishing is never assigned without a fishery. + +For each labor a preference order is calculated based on skill, biased against masters of other +trades and excluding those who can't do the job. The labor is then added to the best +dwarves for that labor. We assign at least the minimum number of dwarfs, in order of preference, +and then assign additional dwarfs that meet any of these conditions: + +* The dwarf is idle and there are no idle dwarves assigned to this labor +* The dwarf has non-zero skill associated with the labor +* The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled. + +We stop assigning dwarfs when we reach the maximum allowed. + +Advanced usage: + +:autolabor []: + Set number of dwarves assigned to a labor. +:autolabor haulers: Set a labor to be handled by hauler dwarves. +:autolabor disable: Turn off autolabor for a specific labor. +:autolabor reset: Return a labor to the default handling. +:autolabor reset-all: Return all labors to the default handling. +:autolabor list: List current status of all labors. +:autolabor status: Show basic status information. + +See `autolabor-artisans` for a differently-tuned setup. + +Examples: + +``autolabor MINE`` + Keep at least 5 dwarves with mining enabled. +``autolabor CUT_GEM 1 1`` + Keep exactly 1 dwarf with gemcutting enabled. +``autolabor COOK 1 1 3`` + Keep 1 dwarf with cooking enabled, selected only from the top 3. +``autolabor FEED_WATER_CIVILIANS haulers`` + Have haulers feed and water wounded dwarves. +``autolabor CUTWOOD disable`` + Turn off autolabor for wood cutting. diff --git a/docs/plugins/automaterial.rst b/docs/plugins/automaterial.rst new file mode 100644 index 000000000..81de0cb3d --- /dev/null +++ b/docs/plugins/automaterial.rst @@ -0,0 +1,33 @@ +automaterial +============ +This makes building constructions (walls, floors, fortifications, etc) a little bit +easier by saving you from having to trawl through long lists of materials each time +you place one. + +Firstly, it moves the last used material for a given construction type to the top of +the list, if there are any left. So if you build a wall with chalk blocks, the next +time you place a wall the chalk blocks will be at the top of the list, regardless of +distance (it only does this in "grouped" mode, as individual item lists could be huge). +This should mean you can place most constructions without having to search for your +preferred material type. + +.. image:: ../images/automaterial-mat.png + +Pressing :kbd:`a` while highlighting any material will enable that material for "auto select" +for this construction type. You can enable multiple materials as autoselect. Now the next +time you place this type of construction, the plugin will automatically choose materials +for you from the kinds you enabled. If there is enough to satisfy the whole placement, +you won't be prompted with the material screen - the construction will be placed and you +will be back in the construction menu as if you did it manually. + +When choosing the construction placement, you will see a couple of options: + +.. image:: ../images/automaterial-pos.png + +Use :kbd:`a` here to temporarily disable the material autoselection, e.g. if you need +to go to the material selection screen so you can toggle some materials on or off. + +The other option (auto type selection, off by default) can be toggled on with :kbd:`t`. If you +toggle this option on, instead of returning you to the main construction menu after selecting +materials, it returns you back to this screen. If you use this along with several autoselect +enabled materials, you should be able to place complex constructions more conveniently. diff --git a/docs/plugins/automelt.rst b/docs/plugins/automelt.rst new file mode 100644 index 000000000..73f8ba502 --- /dev/null +++ b/docs/plugins/automelt.rst @@ -0,0 +1,5 @@ +automelt +======== +When automelt is enabled for a stockpile, any meltable items placed +in it will be designated to be melted. +This plugin adds an option to the :kbd:`q` menu when `enabled `. diff --git a/docs/plugins/autonestbox.rst b/docs/plugins/autonestbox.rst new file mode 100644 index 000000000..f19cad42c --- /dev/null +++ b/docs/plugins/autonestbox.rst @@ -0,0 +1,19 @@ +autonestbox +=========== +Assigns unpastured female egg-layers to nestbox zones. Requires that you create +pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox +must be in the top left corner. Only 1 unit will be assigned per pen, regardless +of the size. The age of the units is currently not checked, most birds grow up +quite fast. Egglayers who are also grazers will be ignored, since confining them +to a 1x1 pasture is not a good idea. Only tame and domesticated own units are +processed since pasturing half-trained wild egglayers could destroy your neat +nestbox zones when they revert to wild. When called without options autonestbox +will instantly run once. + +Options: + +:start: Start running every X frames (df simulation ticks). + Default: X=6000, which would be every 60 seconds at 100fps. +:stop: Stop running automatically. +:sleep: Must be followed by number X. Changes the timer to sleep X + frames between runs. diff --git a/docs/plugins/autotrade.rst b/docs/plugins/autotrade.rst new file mode 100644 index 000000000..4f0d95efd --- /dev/null +++ b/docs/plugins/autotrade.rst @@ -0,0 +1,5 @@ +autotrade +========= +When autotrade is enabled for a stockpile, any items placed in it will be +designated to be taken to the Trade Depot whenever merchants are on the map. +This plugin adds an option to the :kbd:`q` menu when `enabled `. diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst new file mode 100644 index 000000000..6d5e306d1 --- /dev/null +++ b/docs/plugins/blueprint.rst @@ -0,0 +1,112 @@ +blueprint +========= +The ``blueprint`` command exports the structure of a portion of your fortress in +a blueprint file that you (or anyone else) can later play back with `quickfort`. + +Blueprints are ``.csv`` or ``.xlsx`` files created in the ``blueprints`` +subdirectory of your DF folder. The map area to turn into a blueprint is either +selected interactively with the ``blueprint gui`` command or, if the GUI is not +used, starts at the active cursor location and extends right and down for the +requested width and height. + +**Usage:** + + ``blueprint [] [ []] []`` + + ``blueprint gui [ []] []`` + +**Examples:** + +``blueprint gui`` + Runs `gui/blueprint`, the interactive frontend, where all configuration for + a ``blueprint`` command can be set visually and interactively. + +``blueprint 30 40 bedrooms`` + Generates blueprints for an area 30 tiles wide by 40 tiles tall, starting + from the active cursor on the current z-level. Blueprints are written + sequentially to ``bedrooms.csv`` in the ``blueprints`` directory. + +``blueprint 30 40 bedrooms dig --cursor 108,100,150`` + Generates only the ``#dig`` blueprint in the ``bedrooms.csv`` file, and + the start of the blueprint area is set to a specific value instead of using + the in-game cursor position. + +**Positional Parameters:** + +:``width``: Width of the area (in tiles) to translate. +:``height``: Height of the area (in tiles) to translate. +:``depth``: Number of z-levels to translate. Positive numbers go *up* from the + cursor and negative numbers go *down*. Defaults to 1 if not specified, + indicating that the blueprint should only include the current z-level. +:``name``: Base name for blueprint files created in the ``blueprints`` + directory. If no name is specified, "blueprint" is used by default. The + string must contain some characters other than numbers so the name won't be + confused with the optional ``depth`` parameter. + +**Phases:** + +If you want to generate blueprints only for specific phases, add their names to +the commandline, anywhere after the blueprint base name. You can list multiple +phases; just separate them with a space. + +:``dig``: Generate quickfort ``#dig`` blueprints for digging natural stone. +:``carve``: Generate quickfort ``#dig`` blueprints for smoothing and carving. +:``build``: Generate quickfort ``#build`` blueprints for constructions and + buildings. +:``place``: Generate quickfort ``#place`` blueprints for placing stockpiles. +:``zone``: Generate quickfort ``#zone`` blueprints for designating zones. +:``query``: Generate quickfort ``#query`` blueprints for configuring rooms. + +If no phases are specified, phases are autodetected. For example, a ``#place`` +blueprint will be created only if there are stockpiles in the blueprint area. + +**Options:** + +``-c``, ``--cursor ,,``: + Use the specified map coordinates instead of the current cursor position for + the upper left corner of the blueprint range. If this option is specified, + then an active game map cursor is not necessary. +``-e``, ``--engrave``: + Record engravings in the ``carve`` phase. If this option is not specified, + engravings are ignored. +``-f``, ``--format ``: + Select the output format of the generated files. See the ``Output formats`` + section below for options. If not specified, the output format defaults to + "minimal", which will produce a small, fast ``.csv`` file. +``-h``, ``--help``: + Show command help text. +``-s``, ``--playback-start ,,``: + Specify the column and row offsets (relative to the upper-left corner of the + blueprint, which is ``1,1``) where the player should put the cursor when the + blueprint is played back with `quickfort`, in + `quickfort start marker ` format, for example: + ``10,10,central stairs``. If there is a space in the comment, you will need + to surround the parameter string in double quotes: ``"-s10,10,central stairs"`` or + ``--playback-start "10,10,central stairs"`` or + ``"--playback-start=10,10,central stairs"``. +``-t``, ``--splitby ``: + Split blueprints into multiple files. See the ``Splitting output into + multiple files`` section below for details. If not specified, defaults to + "none", which will create a standard quickfort + `multi-blueprint ` file. + +**Output formats:** + +Here are the values that can be passed to the ``--format`` flag: + +:``minimal``: + Creates ``.csv`` files with minimal file size that are fast to read and + write. This is the default. +:``pretty``: + Makes the blueprints in the ``.csv`` files easier to read and edit with a text + editor by adding extra spacing and alignment markers. + +**Splitting output into multiple files:** + +The ``--splitby`` flag can take any of the following values: + +:``none``: + Writes all blueprints into a single file. This is the standard format for + quickfort fortress blueprint bundles and is the default. +:``phase``: + Creates a separate file for each phase. diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst new file mode 100644 index 000000000..ca6273d10 --- /dev/null +++ b/docs/plugins/buildingplan.rst @@ -0,0 +1,88 @@ +buildingplan +============ +When active (via ``enable buildingplan``), this plugin adds a planning mode for +building placement. You can then place furniture, constructions, and other buildings +before the required materials are available, and they will be created in a suspended +state. Buildingplan will periodically scan for appropriate items, and the jobs will +be unsuspended when the items are available. + +This is very useful when combined with `workflow` - you can set a constraint +to always have one or two doors/beds/tables/chairs/etc available, and place +as many as you like. The plugins then take over and fulfill the orders, +with minimal space dedicated to stockpiles. + +.. _buildingplan-filters: + +Item filtering +-------------- + +While placing a building, you can set filters for what materials you want the building made +out of, what quality you want the component items to be, and whether you want the items to +be decorated. + +If a building type takes more than one item to construct, use :kbd:`Ctrl`:kbd:`Left` and +:kbd:`Ctrl`:kbd:`Right` to select the item that you want to set filters for. Any filters that +you set will be used for all buildings of the selected type placed from that point onward +(until you set a new filter or clear the current one). Buildings placed before the filters +were changed will keep the filter values that were set when the building was placed. + +For example, you can be sure that all your constructed walls are the same color by setting +a filter to accept only certain types of stone. + +Quickfort mode +-------------- + +If you use the external Python Quickfort to apply building blueprints instead of the native +DFHack `quickfort` script, you must enable Quickfort mode. This temporarily enables +buildingplan for all building types and adds an extra blank screen after every building +placement. This "dummy" screen is needed for Python Quickfort to interact successfully with +Dwarf Fortress. + +Note that Quickfort mode is only for compatibility with the legacy Python Quickfort. The +DFHack `quickfort` script does not need Quickfort mode to be enabled. The `quickfort` script +will successfully integrate with buildingplan as long as the buildingplan plugin is enabled. + +.. _buildingplan-settings: + +Global settings +--------------- + +The buildingplan plugin has several global settings that can be set from the UI (:kbd:`G` +from any building placement screen, for example: :kbd:`b`:kbd:`a`:kbd:`G`). These settings +can also be set from the ``DFHack#`` prompt once a map is loaded (or from your +``onMapLoad.init`` file) with the syntax:: + + buildingplan set + +and displayed with:: + + buildingplan set + +The available settings are: + ++----------------+---------+-----------+---------------------------------------+ +| Setting | Default | Persisted | Description | ++================+=========+===========+=======================================+ +| all_enabled | false | no | Enable planning mode for all building | +| | | | types. | ++----------------+---------+-----------+---------------------------------------+ +| blocks | true | yes | Allow blocks, boulders, logs, or bars | ++----------------+---------+ | to be matched for generic "building | +| boulders | true | | material" items | ++----------------+---------+ | | +| logs | true | | | ++----------------+---------+ | | +| bars | false | | | ++----------------+---------+-----------+---------------------------------------+ +| quickfort_mode | false | no | Enable compatibility mode for the | +| | | | legacy Python Quickfort (not required | +| | | | for DFHack quickfort) | ++----------------+---------+-----------+---------------------------------------+ + +For example, to ensure you only use blocks when a "building material" item is required, you +could add this to your ``onMapLoad.init`` file:: + + on-new-fortress buildingplan set boulders false; buildingplan set logs false + +Persisted settings (i.e. ``blocks``, ``boulders``, ``logs``, and ``bars``) are saved with +your game, so you only need to set them to the values you want once. diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst new file mode 100644 index 000000000..787b3ff37 --- /dev/null +++ b/docs/plugins/burrows.rst @@ -0,0 +1,38 @@ +burrows +======= +Miscellaneous burrow control. Allows manipulating burrows and automated burrow +expansion while digging. + +Options: + +:enable feature ...: + Enable features of the plugin. +:disable feature ...: + Disable features of the plugin. +:clear-unit burrow burrow ...: + Remove all units from the burrows. +:clear-tiles burrow burrow ...: + Remove all tiles from the burrows. +:set-units target-burrow src-burrow ...: + Clear target, and adds units from source burrows. +:add-units target-burrow src-burrow ...: + Add units from the source burrows to the target. +:remove-units target-burrow src-burrow ...: + Remove units in source burrows from the target. +:set-tiles target-burrow src-burrow ...: + Clear target and adds tiles from the source burrows. +:add-tiles target-burrow src-burrow ...: + Add tiles from the source burrows to the target. +:remove-tiles target-burrow src-burrow ...: + Remove tiles in source burrows from the target. + + For these three options, in place of a source burrow it is + possible to use one of the following keywords: ABOVE_GROUND, + SUBTERRANEAN, INSIDE, OUTSIDE, LIGHT, DARK, HIDDEN, REVEALED + +Features: + +:auto-grow: When a wall inside a burrow with a name ending in '+' is dug + out, the burrow is extended to newly-revealed adjacent walls. + This final '+' may be omitted in burrow name args of commands above. + Digging 1-wide corridors with the miner inside the burrow is SLOW. diff --git a/docs/plugins/changeitem.rst b/docs/plugins/changeitem.rst new file mode 100644 index 000000000..c6da2516f --- /dev/null +++ b/docs/plugins/changeitem.rst @@ -0,0 +1,26 @@ +changeitem +========== +Allows changing item material and base quality. By default the item currently +selected in the UI will be changed (you can select items in the 'k' list +or inside containers/inventory). By default change is only allowed if materials +is of the same subtype (for example wood<->wood, stone<->stone etc). But since +some transformations work pretty well and may be desired you can override this +with 'force'. Note that some attributes will not be touched, possibly resulting +in weirdness. To get an idea how the RAW id should look like, check some items +with 'info'. Using 'force' might create items which are not touched by +crafters/haulers. + +Options: + +:info: Don't change anything, print some info instead. +:here: Change all items at the cursor position. Requires in-game cursor. +:material, m: Change material. Must be followed by valid material RAW id. +:quality, q: Change base quality. Must be followed by number (0-5). +:force: Ignore subtypes, force change to new material. + +Examples: + +``changeitem m INORGANIC:GRANITE here`` + Change material of all items under the cursor to granite. +``changeitem q 5`` + Change currently selected item to masterpiece quality. diff --git a/docs/plugins/changelayer.rst b/docs/plugins/changelayer.rst new file mode 100644 index 000000000..87c27921c --- /dev/null +++ b/docs/plugins/changelayer.rst @@ -0,0 +1,60 @@ +changelayer +=========== +Changes material of the geology layer under cursor to the specified inorganic +RAW material. Can have impact on all surrounding regions, not only your embark! +By default changing stone to soil and vice versa is not allowed. By default +changes only the layer at the cursor position. Note that one layer can stretch +across lots of z levels. By default changes only the geology which is linked +to the biome under the cursor. That geology might be linked to other biomes +as well, though. Mineral veins and gem clusters will stay on the map. Use +`changevein` for them. + +tl;dr: You will end up with changing quite big areas in one go, especially if +you use it in lower z levels. Use with care. + +Options: + +:all_biomes: Change selected layer for all biomes on your map. + Result may be undesirable since the same layer can AND WILL + be on different z-levels for different biomes. Use the tool + 'probe' to get an idea how layers and biomes are distributed + on your map. +:all_layers: Change all layers on your map (only for the selected biome + unless 'all_biomes' is added). + Candy mountain, anyone? Will make your map quite boring, + but tidy. +:force: Allow changing stone to soil and vice versa. !!THIS CAN HAVE + WEIRD EFFECTS, USE WITH CARE!! + Note that soil will not be magically replaced with stone. + You will, however, get a stone floor after digging so it + will allow the floor to be engraved. + Note that stone will not be magically replaced with soil. + You will, however, get a soil floor after digging so it + could be helpful for creating farm plots on maps with no + soil. +:verbose: Give some details about what is being changed. +:trouble: Give some advice about known problems. + +Examples: + +``changelayer GRANITE`` + Convert layer at cursor position into granite. +``changelayer SILTY_CLAY force`` + Convert layer at cursor position into clay even if it's stone. +``changelayer MARBLE all_biomes all_layers`` + Convert all layers of all biomes which are not soil into marble. + +.. note:: + + * If you use changelayer and nothing happens, try to pause/unpause the game + for a while and try to move the cursor to another tile. Then try again. + If that doesn't help try temporarily changing some other layer, undo your + changes and try again for the layer you want to change. Saving + and reloading your map might also help. + * You should be fine if you only change single layers without the use + of 'force'. Still it's advisable to save your game before messing with + the map. + * When you force changelayer to convert soil to stone you might experience + weird stuff (flashing tiles, tiles changed all over place etc). + Try reverting the changes manually or even better use an older savegame. + You did save your game, right? diff --git a/docs/plugins/changevein.rst b/docs/plugins/changevein.rst new file mode 100644 index 000000000..ad7ab3d54 --- /dev/null +++ b/docs/plugins/changevein.rst @@ -0,0 +1,10 @@ +changevein +========== +Changes material of the vein under cursor to the specified inorganic RAW +material. Only affects tiles within the current 16x16 block - for veins and +large clusters, you will need to use this command multiple times. + +Example: + +``changevein NATIVE_PLATINUM`` + Convert vein at cursor position into platinum ore. diff --git a/docs/plugins/clean.rst b/docs/plugins/clean.rst new file mode 100644 index 000000000..f2d0c361d --- /dev/null +++ b/docs/plugins/clean.rst @@ -0,0 +1,16 @@ +clean +===== +Cleans all the splatter that get scattered all over the map, items and +creatures. In an old fortress, this can significantly reduce FPS lag. It can +also spoil your !!FUN!!, so think before you use it. + +Options: + +:map: Clean the map tiles. By default, it leaves mud and snow alone. +:units: Clean the creatures. Will also clean hostiles. +:items: Clean all the items. Even a poisoned blade. + +Extra options for ``map``: + +:mud: Remove mud in addition to the normal stuff. +:snow: Also remove snow coverings. diff --git a/docs/plugins/cleanconst.rst b/docs/plugins/cleanconst.rst new file mode 100644 index 000000000..a8169d83b --- /dev/null +++ b/docs/plugins/cleanconst.rst @@ -0,0 +1,7 @@ +cleanconst +========== +Cleans up construction materials. + +This utility alters all constructions on the map so that they spawn their +building component when they are disassembled, allowing their actual +build items to be safely deleted. This can improve FPS in extreme situations. diff --git a/docs/plugins/cleanowned.rst b/docs/plugins/cleanowned.rst new file mode 100644 index 000000000..7f80e35cf --- /dev/null +++ b/docs/plugins/cleanowned.rst @@ -0,0 +1,19 @@ +cleanowned +========== +Confiscates items owned by dwarfs. By default, owned food on the floor +and rotten items are confistacted and dumped. + +Options: + +:all: confiscate all owned items +:scattered: confiscated and dump all items scattered on the floor +:x: confiscate/dump items with wear level 'x' and more +:X: confiscate/dump items with wear level 'X' and more +:dryrun: a dry run. combine with other options to see what will happen + without it actually happening. + +Example: + +``cleanowned scattered X`` + This will confiscate rotten and dropped food, garbage on the floors and any + worn items with 'X' damage and above. diff --git a/docs/plugins/command-prompt.rst b/docs/plugins/command-prompt.rst new file mode 100644 index 000000000..061b405ef --- /dev/null +++ b/docs/plugins/command-prompt.rst @@ -0,0 +1,16 @@ +command-prompt +============== +An in-game DFHack terminal, where you can enter other commands. + +:dfhack-keybind:`command-prompt` + +Usage: ``command-prompt [entry]`` + +If called with an entry, it starts with that text filled in. +Most useful for developers, who can set a keybinding to open +a laungage interpreter for lua or Ruby by starting with the +`:lua ` or `:rb ` commands. + +Otherwise somewhat similar to `gui/quickcmd`. + +.. image:: ../images/command-prompt.png diff --git a/docs/plugins/confirm.rst b/docs/plugins/confirm.rst new file mode 100644 index 000000000..c7fb89bb0 --- /dev/null +++ b/docs/plugins/confirm.rst @@ -0,0 +1,12 @@ +confirm +======= +Implements several confirmation dialogs for potentially destructive actions +(for example, seizing goods from traders or deleting hauling routes). + +Usage: + +:enable confirm: Enable all confirmations; alias ``confirm enable all``. + Replace with ``disable`` to disable. +:confirm help: List available confirmation dialogues. +:confirm enable option1 [option2...]: + Enable (or disable) specific confirmation dialogues. diff --git a/docs/plugins/createitem.rst b/docs/plugins/createitem.rst new file mode 100644 index 000000000..f37154f80 --- /dev/null +++ b/docs/plugins/createitem.rst @@ -0,0 +1,48 @@ +createitem +========== +Allows creating new items of arbitrary types and made of arbitrary materials. A +unit must be selected in-game to use this command. By default, items created are +spawned at the feet of the selected unit. + +Specify the item and material information as you would indicate them in +custom reaction raws, with the following differences: + +* Separate the item and material with a space rather than a colon +* If the item has no subtype, the ``:NONE`` can be omitted +* If the item is ``REMAINS``, ``FISH``, ``FISH_RAW``, ``VERMIN``, ``PET``, or ``EGG``, + specify a ``CREATURE:CASTE`` pair instead of a material token. +* If the item is a ``PLANT_GROWTH``, specify a ``PLANT_ID:GROWTH_ID`` pair + instead of a material token. + +Corpses, body parts, and prepared meals cannot be created using this tool. + +To obtain the item and material tokens of an existing item, run +``createitem inspect``. Its output can be passed directly as arguments to +``createitem`` to create new matching items, as long as the item type is +supported. + +Examples: + +* Create 2 pairs of steel gauntlets:: + + createitem GLOVES:ITEM_GLOVES_GAUNTLETS INORGANIC:STEEL 2 + +* Create tower-cap logs:: + + createitem WOOD PLANT_MAT:TOWER_CAP:WOOD + +* Create bilberries:: + + createitem PLANT_GROWTH BILBERRY:FRUIT + +For more examples, :wiki:`see this wiki page `. + +To change where new items are placed, first run the command with a +destination type while an appropriate destination is selected. + +Options: + +:floor: Subsequent items will be placed on the floor beneath the selected unit's feet. +:item: Subsequent items will be stored inside the currently selected item. +:building: Subsequent items will become part of the currently selected building. + Good for loading traps; do not use with workshops (or deconstruct to use the item). diff --git a/docs/plugins/cursecheck.rst b/docs/plugins/cursecheck.rst new file mode 100644 index 000000000..1bf1803fa --- /dev/null +++ b/docs/plugins/cursecheck.rst @@ -0,0 +1,42 @@ +cursecheck +========== +Checks a single map tile or the whole map/world for cursed creatures (ghosts, +vampires, necromancers, werebeasts, zombies). + +With an active in-game cursor only the selected tile will be observed. +Without a cursor the whole map will be checked. + +By default cursed creatures will be only counted in case you just want to find +out if you have any of them running around in your fort. Dead and passive +creatures (ghosts who were put to rest, killed vampires, ...) are ignored. +Undead skeletons, corpses, bodyparts and the like are all thrown into the curse +category "zombie". Anonymous zombies and resurrected body parts will show +as "unnamed creature". + +Options: + +:detail: Print full name, date of birth, date of curse and some status + info (some vampires might use fake identities in-game, though). +:ids: Print the creature and race IDs. +:nick: Set the type of curse as nickname (does not always show up + in-game, some vamps don't like nicknames). +:all: Include dead and passive cursed creatures (can result in a quite + long list after having FUN with necromancers). +:verbose: Print all curse tags (if you really want to know it all). + +Examples: + +``cursecheck detail all`` + Give detailed info about all cursed creatures including deceased ones (no + in-game cursor). +``cursecheck nick`` + Give a nickname all living/active cursed creatures on the map(no in-game + cursor). + +.. note:: + + If you do a full search (with the option "all") former ghosts will show up + with the cursetype "unknown" because their ghostly flag is not set. + + Please report any living/active creatures with cursetype "unknown" - + this is most likely with mods which introduce new types of curses. diff --git a/docs/plugins/debug.rst b/docs/plugins/debug.rst new file mode 100644 index 000000000..2bef7d28a --- /dev/null +++ b/docs/plugins/debug.rst @@ -0,0 +1,89 @@ +debug +===== +Manager for DFHack runtime debug prints. Debug prints are grouped by plugin name, +category name and print level. Levels are ``trace``, ``debug``, ``info``, +``warning`` and ``error``. + +The runtime message printing is controlled using filters. Filters set the +visible messages of all matching categories. Matching uses regular expression syntax, +which allows listing multiple alternative matches or partial name matches. +This syntax is a C++ version of the ECMA-262 grammar (Javascript regular expressions). +Details of differences can be found at +https://en.cppreference.com/w/cpp/regex/ecmascript + +Persistent filters are stored in ``dfhack-config/runtime-debug.json``. +Oldest filters are applied first. That means a newer filter can override the +older printing level selection. + +Usage: ``debugfilter [subcommand] [parameters...]`` + +The following subcommands are supported: + +help +---- +Give overall help or a detailed help for a subcommand. + +Usage: ``debugfilter help [subcommand]`` + +category +-------- +List available debug plugin and category names. + +Usage: ``debugfilter category [plugin regex] [category regex]`` + +The list can be filtered using optional regex parameters. If filters aren't +given then the it uses ``"."`` regex which matches any character. The regex +parameters are good way to test regex before passing them to ``set``. + +filter +------ +List active and passive debug print level changes. + +Usage: ``debugfilter filter [id]`` + +Optional ``id`` parameter is the id listed as first column in the filter list. +If id is given then the command shows information for the given filter only in +multi line format that is better format if filter has long regex. + +set +--- +Creates a new debug filter to set category printing levels. + +Usage: ``debugfilter set [level] [plugin regex] [category regex]`` + +Adds a filter that will be deleted when DF process exists or plugin is unloaded. + +Usage: ``debugfilter set persistent [level] [plugin regex] [category regex]`` + +Stores the filter in the configuration file to until ``unset`` is used to remove +it. + +Level is the minimum debug printing level to show in log. + +* ``trace``: Possibly very noisy messages which can be printed many times per second + +* ``debug``: Messages that happen often but they should happen only a couple of times per second + +* ``info``: Important state changes that happen rarely during normal execution + +* ``warning``: Enabled by default. Shows warnings about unexpected events which code managed to handle correctly. + +* ``error``: Enabled by default. Shows errors which code can't handle without user intervention. + +unset +----- +Delete a space separated list of filters + +Usage: ``debugfilter unset [id...]`` + +disable +------- +Disable a space separated list of filters but keep it in the filter list + +Usage: ``debugfilter disable [id...]`` + +enable +------ +Enable a space sperate list of filters + +Usage: ``debugfilter enable [id...]`` diff --git a/docs/plugins/deramp.rst b/docs/plugins/deramp.rst new file mode 100644 index 000000000..a6b944381 --- /dev/null +++ b/docs/plugins/deramp.rst @@ -0,0 +1,6 @@ +deramp +====== +Removes all ramps designated for removal from the map. This is useful for +replicating the old channel digging designation. It also removes any and +all 'down ramps' that can remain after a cave-in (you don't have to designate +anything for that to happen). diff --git a/docs/plugins/dig-now.rst b/docs/plugins/dig-now.rst new file mode 100644 index 000000000..b6f4d64f7 --- /dev/null +++ b/docs/plugins/dig-now.rst @@ -0,0 +1,61 @@ +dig-now +======= + +Instantly completes non-marker dig designations, modifying tile shapes and +creating boulders, ores, and gems as if a miner were doing the mining or +engraving. By default, the entire map is processed and boulder generation +follows standard game rules, but the behavior is configurable. + +Note that no units will get mining or engraving experience for the dug/engraved +tiles. + +Trees and roots are not currently handled by this plugin and will be skipped. +Requests for engravings are also skipped since they would depend on the skill +and creative choices of individual engravers. Other types of engraving (i.e. +smoothing and track carving) are handled. + +Usage:: + + dig-now [ []] [] + +Where the optional ```` pair can be used to specify the coordinate bounds +within which ``dig-now`` will operate. If they are not specified, ``dig-now`` +will scan the entire map. If only one ```` is specified, only the tile at +that coordinate is processed. + +Any ```` parameters can either be an ``,,`` triple (e.g. +``35,12,150``) or the string ``here``, which means the position of the active +game cursor should be used. + +Examples: + +``dig-now`` + Dig designated tiles according to standard game rules. + +``dig-now --clean`` + Dig designated tiles, but don't generate any boulders, ores, or gems. + +``dig-now --dump here`` + Dig tiles and dump all generated boulders, ores, and gems at the tile under + the game cursor. + +Options: + +:``-c``, ``--clean``: + Don't generate any boulders, ores, or gems. Equivalent to + ``--percentages 0,0,0,0``. +:``-d``, ``--dump ``: + Dump any generated items at the specified coordinates. If the tile at those + coordinates is open space or is a wall, items will be generated on the + closest walkable tile below. +:``-e``, ``--everywhere``: + Generate a boulder, ore, or gem for every tile that can produce one. + Equivalent to ``--percentages 100,100,100,100``. +:``-h``, ``--help``: + Show quick usage help text. +:``-p``, ``--percentages ,,,``: + Set item generation percentages for each of the tile categories. The + ``vein`` category includes both the large oval clusters and the long stringy + mineral veins. Default is ``25,33,100,100``. +:``-z``, ``--cur-zlevel``: + Restricts the bounds to the currently visible z-level. diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst new file mode 100644 index 000000000..f09626d0c --- /dev/null +++ b/docs/plugins/dig.rst @@ -0,0 +1,20 @@ +dig +=== +This plugin makes many automated or complicated dig patterns easy. + +Basic commands: + +:digv: Designate all of the selected vein for digging. +:digvx: Also cross z-levels, digging stairs as needed. Alias for ``digv x``. +:digl: Like ``digv``, for layer stone. Also supports an ``undo`` option + to remove designations, for if you accidentally set 50 levels at once. +:diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. + +:dfhack-keybind:`digv` + +.. note:: + + All commands implemented by the `dig` plugin (listed by ``ls dig``) support + specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, + where ``#`` is a number from 1 to 7. If a priority is not specified, the + priority selected in-game is used as the default. diff --git a/docs/plugins/digFlood.rst b/docs/plugins/digFlood.rst new file mode 100644 index 000000000..dd957feb2 --- /dev/null +++ b/docs/plugins/digFlood.rst @@ -0,0 +1,18 @@ +digFlood +======== +Automatically digs out specified veins as they are discovered. It runs once +every time a dwarf finishes a dig job. It will only dig out appropriate tiles +that are adjacent to the finished dig job. To add a vein type, use ``digFlood 1 [type]``. +This will also enable the plugin. To remove a vein type, use ``digFlood 0 [type] 1`` +to disable, then remove, then re-enable. + +Usage: + +:help digflood: detailed help message +:digFlood 0: disable the plugin +:digFlood 1: enable the plugin +:digFlood 0 MICROCLINE COAL_BITUMINOUS 1: + disable plugin, remove microcline and bituminous coal from monitoring, then re-enable the plugin +:digFlood CLEAR: remove all inorganics from monitoring +:digFlood digAll1: ignore the monitor list and dig any vein +:digFlood digAll0: disable digAll mode diff --git a/docs/plugins/digcircle.rst b/docs/plugins/digcircle.rst new file mode 100644 index 000000000..fc31b65df --- /dev/null +++ b/docs/plugins/digcircle.rst @@ -0,0 +1,35 @@ +digcircle +========= +A command for easy designation of filled and hollow circles. +It has several types of options. + +Shape: + +:hollow: Set the circle to hollow (default) +:filled: Set the circle to filled +:#: Diameter in tiles (default = 0, does nothing) + +Action: + +:set: Set designation (default) +:unset: Unset current designation +:invert: Invert designations already present + +Designation types: + +:dig: Normal digging designation (default) +:ramp: Ramp digging +:ustair: Staircase up +:dstair: Staircase down +:xstair: Staircase up/down +:chan: Dig channel + +After you have set the options, the command called with no options +repeats with the last selected parameters. + +Examples: + +``digcircle filled 3`` + Dig a filled circle with diameter = 3. +``digcircle`` + Do it again. diff --git a/docs/plugins/digexp.rst b/docs/plugins/digexp.rst new file mode 100644 index 000000000..e4412d051 --- /dev/null +++ b/docs/plugins/digexp.rst @@ -0,0 +1,31 @@ +digexp +====== +This command is for :wiki:`exploratory mining `. + +There are two variables that can be set: pattern and filter. + +Patterns: + +:diag5: diagonals separated by 5 tiles +:diag5r: diag5 rotated 90 degrees +:ladder: A 'ladder' pattern +:ladderr: ladder rotated 90 degrees +:clear: Just remove all dig designations +:cross: A cross, exactly in the middle of the map. + +Filters: + +:all: designate whole z-level +:hidden: designate only hidden tiles of z-level (default) +:designated: Take current designation and apply pattern to it. + +After you have a pattern set, you can use ``expdig`` to apply it again. + +Examples: + +``expdig diag5 hidden`` + Designate the diagonal 5 patter over all hidden tiles +``expdig`` + Apply last used pattern and filter +``expdig ladder designated`` + Take current designations and replace them with the ladder pattern diff --git a/docs/plugins/diggingInvaders.rst b/docs/plugins/diggingInvaders.rst new file mode 100644 index 000000000..0557225fb --- /dev/null +++ b/docs/plugins/diggingInvaders.rst @@ -0,0 +1,29 @@ +diggingInvaders +=============== +Makes invaders dig or destroy constructions to get to your dwarves. + +To enable/disable the pluging, use: ``diggingInvaders (1|enable)|(0|disable)`` + +Basic usage: + +:add GOBLIN: registers the race GOBLIN as a digging invader. Case-sensitive. +:remove GOBLIN: unregisters the race GOBLIN as a digging invader. Case-sensitive. +:now: makes invaders try to dig now, if plugin is enabled +:clear: clears all digging invader races +:edgesPerTick n: makes the pathfinding algorithm work on at most n edges per tick. + Set to 0 or lower to make it unlimited. + +You can also use ``diggingInvaders setCost (race) (action) n`` to set the +pathing cost of particular action, or ``setDelay`` to set how long it takes. +Costs and delays are per-tile, and the table shows default values. + +============================== ======= ====== ================================= +Action Cost Delay Notes +============================== ======= ====== ================================= +``walk`` 1 0 base cost in the path algorithm +``destroyBuilding`` 2 1,000 delay adds to the job_completion_timer of destroy building jobs that are assigned to invaders +``dig`` 10,000 1,000 digging soil or natural stone +``destroyRoughConstruction`` 1,000 1,000 constructions made from boulders +``destroySmoothConstruction`` 100 100 constructions made from blocks or bars +============================== ======= ====== ================================= + diff --git a/docs/plugins/digl.rst b/docs/plugins/digl.rst new file mode 100644 index 000000000..f09626d0c --- /dev/null +++ b/docs/plugins/digl.rst @@ -0,0 +1,20 @@ +dig +=== +This plugin makes many automated or complicated dig patterns easy. + +Basic commands: + +:digv: Designate all of the selected vein for digging. +:digvx: Also cross z-levels, digging stairs as needed. Alias for ``digv x``. +:digl: Like ``digv``, for layer stone. Also supports an ``undo`` option + to remove designations, for if you accidentally set 50 levels at once. +:diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. + +:dfhack-keybind:`digv` + +.. note:: + + All commands implemented by the `dig` plugin (listed by ``ls dig``) support + specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, + where ``#`` is a number from 1 to 7. If a priority is not specified, the + priority selected in-game is used as the default. diff --git a/docs/plugins/diglx.rst b/docs/plugins/diglx.rst new file mode 100644 index 000000000..f09626d0c --- /dev/null +++ b/docs/plugins/diglx.rst @@ -0,0 +1,20 @@ +dig +=== +This plugin makes many automated or complicated dig patterns easy. + +Basic commands: + +:digv: Designate all of the selected vein for digging. +:digvx: Also cross z-levels, digging stairs as needed. Alias for ``digv x``. +:digl: Like ``digv``, for layer stone. Also supports an ``undo`` option + to remove designations, for if you accidentally set 50 levels at once. +:diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. + +:dfhack-keybind:`digv` + +.. note:: + + All commands implemented by the `dig` plugin (listed by ``ls dig``) support + specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, + where ``#`` is a number from 1 to 7. If a priority is not specified, the + priority selected in-game is used as the default. diff --git a/docs/plugins/digtype.rst b/docs/plugins/digtype.rst new file mode 100644 index 000000000..1c2d49eaa --- /dev/null +++ b/docs/plugins/digtype.rst @@ -0,0 +1,19 @@ +digtype +======= +For every tile on the map of the same vein type as the selected tile, +this command designates it to have the same designation as the +selected tile. If the selected tile has no designation, they will be +dig designated. +If an argument is given, the designation of the selected tile is +ignored, and all appropriate tiles are set to the specified +designation. + +Options: + +:dig: +:channel: +:ramp: +:updown: up/down stairs +:up: up stairs +:down: down stairs +:clear: clear designation diff --git a/docs/plugins/digv.rst b/docs/plugins/digv.rst new file mode 100644 index 000000000..f09626d0c --- /dev/null +++ b/docs/plugins/digv.rst @@ -0,0 +1,20 @@ +dig +=== +This plugin makes many automated or complicated dig patterns easy. + +Basic commands: + +:digv: Designate all of the selected vein for digging. +:digvx: Also cross z-levels, digging stairs as needed. Alias for ``digv x``. +:digl: Like ``digv``, for layer stone. Also supports an ``undo`` option + to remove designations, for if you accidentally set 50 levels at once. +:diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. + +:dfhack-keybind:`digv` + +.. note:: + + All commands implemented by the `dig` plugin (listed by ``ls dig``) support + specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, + where ``#`` is a number from 1 to 7. If a priority is not specified, the + priority selected in-game is used as the default. diff --git a/docs/plugins/digvx.rst b/docs/plugins/digvx.rst new file mode 100644 index 000000000..f09626d0c --- /dev/null +++ b/docs/plugins/digvx.rst @@ -0,0 +1,20 @@ +dig +=== +This plugin makes many automated or complicated dig patterns easy. + +Basic commands: + +:digv: Designate all of the selected vein for digging. +:digvx: Also cross z-levels, digging stairs as needed. Alias for ``digv x``. +:digl: Like ``digv``, for layer stone. Also supports an ``undo`` option + to remove designations, for if you accidentally set 50 levels at once. +:diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. + +:dfhack-keybind:`digv` + +.. note:: + + All commands implemented by the `dig` plugin (listed by ``ls dig``) support + specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, + where ``#`` is a number from 1 to 7. If a priority is not specified, the + priority selected in-game is used as the default. diff --git a/docs/plugins/dwarfmonitor.rst b/docs/plugins/dwarfmonitor.rst new file mode 100644 index 000000000..cbe3e8bdc --- /dev/null +++ b/docs/plugins/dwarfmonitor.rst @@ -0,0 +1,78 @@ +dwarfmonitor +============ +Records dwarf activity to measure fort efficiency. + +Options: + +:enable : Start monitoring ``mode``. ``mode`` can be "work", "misery", + "weather", or "all". This will enable all corresponding widgets, + if applicable. +:disable : Stop monitoring ``mode``, and disable corresponding widgets, if applicable. +:stats: Show statistics summary +:prefs: Show dwarf preferences summary +:reload: Reload configuration file (``dfhack-config/dwarfmonitor.json``) + +:dfhack-keybind:`dwarfmonitor` + +Widget configuration: + +The following types of widgets (defined in :file:`hack/lua/plugins/dwarfmonitor.lua`) +can be displayed on the main fortress mode screen: + +:date: Show the in-game date +:misery: Show overall happiness levels of all dwarves +:weather: Show current weather (rain/snow) +:cursor: Show the current mouse cursor position + +The file :file:`dfhack-config/dwarfmonitor.json` can be edited to control the +positions and settings of all widgets displayed. This file should contain a +JSON object with the key ``widgets`` containing an array of objects - see the +included file in the ``dfhack-config`` folder for an example: + +.. code-block:: lua + + { + "widgets": [ + { + "type": "widget type (weather, misery, etc.)", + "x": X coordinate, + "y": Y coordinate + <...additional options...> + } + ] + } + +X and Y coordinates begin at zero (in the upper left corner of the screen). +Negative coordinates will be treated as distances from the lower right corner, +beginning at 1 - e.g. an x coordinate of 0 is the leftmost column, while an x +coordinate of 1 is the rightmost column. + +By default, the x and y coordinates given correspond to the leftmost tile of +the widget. Including an ``anchor`` option set to ``right`` will cause the +rightmost tile of the widget to be located at this position instead. + +Some widgets support additional options: + +* ``date`` widget: + + * ``format``: specifies the format of the date. The following characters + are replaced (all others, such as punctuation, are not modified) + + * ``Y`` or ``y``: The current year + * ``M``: The current month, zero-padded if necessary + * ``m``: The current month, *not* zero-padded + * ``D``: The current day, zero-padded if necessary + * ``d``: The current day, *not* zero-padded + + The default date format is ``Y-M-D``, per the ISO8601_ standard. + + .. _ISO8601: https://en.wikipedia.org/wiki/ISO_8601 + +* ``cursor`` widget: + + * ``format``: Specifies the format. ``X``, ``x``, ``Y``, and ``y`` are + replaced with the corresponding cursor cordinates, while all other + characters are unmodified. + * ``show_invalid``: If set to ``true``, the mouse coordinates will both be + displayed as ``-1`` when the cursor is outside of the DF window; otherwise, + nothing will be displayed. diff --git a/docs/plugins/dwarfvet.rst b/docs/plugins/dwarfvet.rst new file mode 100644 index 000000000..1ceb1bc2f --- /dev/null +++ b/docs/plugins/dwarfvet.rst @@ -0,0 +1,19 @@ +dwarfvet +======== +Enables Animal Caretaker functionality + +Always annoyed your dragons become useless after a minor injury? Well, with +dwarfvet, your animals become first rate members of your fort. It can also +be used to train medical skills. + +Animals need to be treated in an animal hospital, which is simply a hospital +that is also an animal training zone. The console will print out a list on game +load, and whenever one is added or removed. Dwarfs must have the Animal Caretaker +labor to treat animals. Normal medical skills are used (and no experience is given +to the Animal Caretaker skill). + +Options: + +:enable: Enables Animal Caretakers to treat and manage animals +:disable: Turns off the plguin +:report: Reports all zones that the game considers animal hospitals diff --git a/docs/plugins/embark-assistant.rst b/docs/plugins/embark-assistant.rst new file mode 100644 index 000000000..4500a919e --- /dev/null +++ b/docs/plugins/embark-assistant.rst @@ -0,0 +1,9 @@ +embark-assistant +================ + +This plugin provides embark site selection help. It has to be run with the +``embark-assistant`` command while the pre-embark screen is displayed and shows +extended (and correct(?)) resource information for the embark rectangle as well +as normally undisplayed sites in the current embark region. It also has a site +selection tool with more options than DF's vanilla search tool. For detailed +help invoke the in game info screen. diff --git a/docs/plugins/embark-tools.rst b/docs/plugins/embark-tools.rst new file mode 100644 index 000000000..fcab18fc7 --- /dev/null +++ b/docs/plugins/embark-tools.rst @@ -0,0 +1,12 @@ +embark-tools +============ +A collection of embark-related tools. Usage and available tools:: + + embark-tools enable/disable tool [tool]... + +:anywhere: Allows embarking anywhere (including sites, mountain-only biomes, + and oceans). Use with caution. +:mouse: Implements mouse controls (currently in the local embark region only) +:sand: Displays an indicator when sand is present in the currently-selected + area, similar to the default clay/stone indicators. +:sticky: Maintains the selected local area while navigating the world map diff --git a/docs/plugins/fastdwarf.rst b/docs/plugins/fastdwarf.rst new file mode 100644 index 000000000..65d2a3d45 --- /dev/null +++ b/docs/plugins/fastdwarf.rst @@ -0,0 +1,14 @@ +fastdwarf +========= +Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly +and perform tasks quickly. Teledwarf makes dwarves move instantaneously, +but do jobs at the same speed. + +:fastdwarf 0: disables both (also ``0 0``) +:fastdwarf 1: enables speedydwarf and disables teledwarf (also ``1 0``) +:fastdwarf 2: sets a native debug flag in the game memory that implements an + even more aggressive version of speedydwarf. +:fastdwarf 0 1: disables speedydwarf and enables teledwarf +:fastdwarf 1 1: enables both + +See `superdwarf` for a per-creature version. diff --git a/docs/plugins/filltraffic.rst b/docs/plugins/filltraffic.rst new file mode 100644 index 000000000..6a9c57c9f --- /dev/null +++ b/docs/plugins/filltraffic.rst @@ -0,0 +1,17 @@ +filltraffic +=========== +Set traffic designations using flood-fill starting at the cursor. +See also `alltraffic`, `restrictice`, and `restrictliquids`. Options: + +:H: High Traffic +:N: Normal Traffic +:L: Low Traffic +:R: Restricted Traffic +:X: Fill across z-levels. +:B: Include buildings and stockpiles. +:P: Include empty space. + +Example: + +``filltraffic H`` + When used in a room with doors, it will set traffic to HIGH in just that room. diff --git a/docs/plugins/fix-armory.rst b/docs/plugins/fix-armory.rst new file mode 100644 index 000000000..f4b988420 --- /dev/null +++ b/docs/plugins/fix-armory.rst @@ -0,0 +1,4 @@ +fix-armory +========== +`This plugin requires a binpatch `, which has not +been available since DF 0.34.11 diff --git a/docs/plugins/fix-job-postings.rst b/docs/plugins/fix-job-postings.rst new file mode 100644 index 000000000..198dcd61d --- /dev/null +++ b/docs/plugins/fix-job-postings.rst @@ -0,0 +1,5 @@ +fix-job-postings +---------------- +This command fixes crashes caused by previous versions of workflow, mostly in +DFHack 0.40.24-r4, and should be run automatically when loading a world (but can +also be run manually if desired). diff --git a/docs/plugins/fix-unit-occupancy.rst b/docs/plugins/fix-unit-occupancy.rst new file mode 100644 index 000000000..bf999c8a3 --- /dev/null +++ b/docs/plugins/fix-unit-occupancy.rst @@ -0,0 +1,12 @@ +fix-unit-occupancy +================== +This plugin fixes issues with unit occupancy, notably phantom +"unit blocking tile" messages (:bug:`3499`). It can be run manually, or +periodically when enabled with the built-in enable/disable commands: + +:(no argument): Run the plugin once immediately, for the whole map. +:-h, here, cursor: Run immediately, only operate on the tile at the cursor +:-n, dry, dry-run: Run immediately, do not write changes to map +:interval : Run the plugin every ``X`` ticks (when enabled). + The default is 1200 ticks, or 1 day. + Ticks are only counted when the game is unpaused. diff --git a/docs/plugins/fixveins.rst b/docs/plugins/fixveins.rst new file mode 100644 index 000000000..58678d154 --- /dev/null +++ b/docs/plugins/fixveins.rst @@ -0,0 +1,5 @@ +fixveins +======== +Removes invalid references to mineral inclusions and restores missing ones. +Use this if you broke your embark with tools like `tiletypes`, or if you +accidentally placed a construction on top of a valuable mineral floor. diff --git a/docs/plugins/flows.rst b/docs/plugins/flows.rst new file mode 100644 index 000000000..3df451195 --- /dev/null +++ b/docs/plugins/flows.rst @@ -0,0 +1,5 @@ +flows +===== +A tool for checking how many tiles contain flowing liquids. If you suspect that +your magma sea leaks into HFS, you can use this tool to be sure without +revealing the map. diff --git a/docs/plugins/follow.rst b/docs/plugins/follow.rst new file mode 100644 index 000000000..a91c14f73 --- /dev/null +++ b/docs/plugins/follow.rst @@ -0,0 +1,5 @@ +follow +====== +Makes the game view follow the currently highlighted unit after you exit from the +current menu or cursor mode. Handy for watching dwarves running around. Deactivated +by moving the view manually. diff --git a/docs/plugins/forceequip.rst b/docs/plugins/forceequip.rst new file mode 100644 index 000000000..2278bca22 --- /dev/null +++ b/docs/plugins/forceequip.rst @@ -0,0 +1,7 @@ +forceequip +========== +Forceequip moves local items into a unit's inventory. It is typically used to +equip specific clothing/armor items onto a dwarf, but can also be used to put +armor onto a war animal or to add unusual items (such as crowns) to any unit. + +For more information run ``forceequip help``. See also `modtools/equip-item`. diff --git a/docs/plugins/generated-creature-renamer.rst b/docs/plugins/generated-creature-renamer.rst new file mode 100644 index 000000000..bb486a4ee --- /dev/null +++ b/docs/plugins/generated-creature-renamer.rst @@ -0,0 +1,15 @@ +generated-creature-renamer +========================== +Automatically renames generated creatures, such as forgotten beasts, titans, +etc, to have raw token names that match the description given in-game. + +The ``list-generated`` command can be used to list the token names of all +generated creatures in a given save, with an optional ``detailed`` argument +to show the accompanying description. + +The ``save-generated-raws`` command will save a sample creature graphics file in +the Dwarf Fortress root directory, to use as a start for making a graphics set +for generated creatures using the new names that they get with this plugin. + +The new names are saved with the save, and the plugin, when enabled, only runs once +per save, unless there's an update. diff --git a/docs/plugins/getplants.rst b/docs/plugins/getplants.rst new file mode 100644 index 000000000..5a719b3ab --- /dev/null +++ b/docs/plugins/getplants.rst @@ -0,0 +1,37 @@ +getplants +========= +This tool allows plant gathering and tree cutting by RAW ID. Specify the types +of trees to cut down and/or shrubs to gather by their plant names, separated +by spaces. + +Options: + +:``-t``: Tree: Select trees only (exclude shrubs) +:``-s``: Shrub: Select shrubs only (exclude trees) +:``-f``: Farming: Designate only shrubs that yield seeds for farming. Implies -s +:``-c``: Clear: Clear designations instead of setting them +:``-x``: eXcept: Apply selected action to all plants except those specified (invert + selection) +:``-a``: All: Select every type of plant (obeys ``-t``/``-s``/``-f``) +:``-v``: Verbose: Lists the number of (un)designations per plant +:``-n *``: Number: Designate up to * (an integer number) plants of each species + +Specifying both ``-t`` and ``-s`` or ``-f`` will have no effect. If no plant IDs are +specified, all valid plant IDs will be listed, with ``-t``, ``-s``, and ``-f`` +restricting the list to trees, shrubs, and farmable shrubs, respectively. + +.. note:: + + DF is capable of determining that a shrub has already been picked, leaving + an unusable structure part behind. This plugin does not perform such a check + (as the location of the required information has not yet been identified). + This leads to some shrubs being designated when they shouldn't be, causing a + plant gatherer to walk there and do nothing (except clearing the + designation). See :issue:`1479` for details. + + The implementation another known deficiency: it's incapable of detecting that + raw definitions that specify a seed extraction reaction for the structural part + but has no other use for it cannot actually yield any seeds, as the part is + never used (parts of :bug:`6940`, e.g. Red Spinach), even though DF + collects it, unless there's a workshop reaction to do it (which there isn't + in vanilla). diff --git a/docs/plugins/hotkeys.rst b/docs/plugins/hotkeys.rst new file mode 100644 index 000000000..57c3b3af5 --- /dev/null +++ b/docs/plugins/hotkeys.rst @@ -0,0 +1,8 @@ +hotkeys +======= +Opens an in-game screen showing which DFHack keybindings are +active in the current context. See also `hotkey-notes`. + +.. image:: ../images/hotkeys.png + +:dfhack-keybind:`hotkeys` diff --git a/docs/plugins/infiniteSky.rst b/docs/plugins/infiniteSky.rst new file mode 100644 index 000000000..328ee83bd --- /dev/null +++ b/docs/plugins/infiniteSky.rst @@ -0,0 +1,16 @@ +infiniteSky +=========== +Automatically allocates new z-levels of sky at the top of the map as you build up, +or on request allocates many levels all at once. + +Usage: + +``infiniteSky n`` + Raise the sky by n z-levels. +``infiniteSky enable/disable`` + Enables/disables monitoring of constructions. If you build anything in the second to highest z-level, it will allocate one more sky level. This is so you can continue to build stairs upward. + +.. warning:: + + :issue:`Sometimes <254>` new z-levels disappear and cause cave-ins. + Saving and loading after creating new z-levels should fix the problem. diff --git a/docs/plugins/isoworldremote.rst b/docs/plugins/isoworldremote.rst new file mode 100644 index 000000000..7bacc2b57 --- /dev/null +++ b/docs/plugins/isoworldremote.rst @@ -0,0 +1,3 @@ +isoworldremote +============== +A plugin that implements a `remote API ` used by Isoworld. diff --git a/docs/plugins/job-duplicate.rst b/docs/plugins/job-duplicate.rst new file mode 100644 index 000000000..a18d73df3 --- /dev/null +++ b/docs/plugins/job-duplicate.rst @@ -0,0 +1,6 @@ +job-duplicate +============= +In :kbd:`q` mode, when a job is highlighted within a workshop or furnace +building, calling ``job-duplicate`` instantly duplicates the job. + +:dfhack-keybind:`job-duplicate` diff --git a/docs/plugins/job-material.rst b/docs/plugins/job-material.rst new file mode 100644 index 000000000..f1b37fc1d --- /dev/null +++ b/docs/plugins/job-material.rst @@ -0,0 +1,15 @@ +job-material +============ +Alter the material of the selected job. Similar to ``job item-material ...`` + +Invoked as:: + + job-material + +:dfhack-keybind:`job-material` + +* In :kbd:`q` mode, when a job is highlighted within a workshop or furnace, + changes the material of the job. Only inorganic materials can be used + in this mode. +* In :kbd:`b` mode, during selection of building components positions the cursor + over the first available choice with the matching material. diff --git a/docs/plugins/job.rst b/docs/plugins/job.rst new file mode 100644 index 000000000..26f163864 --- /dev/null +++ b/docs/plugins/job.rst @@ -0,0 +1,15 @@ +job +=== +Command for general job query and manipulation. + +Options: + +*no extra options* + Print details of the current job. The job can be selected + in a workshop, or the unit/jobs screen. +**list** + Print details of all jobs in the selected workshop. +**item-material ** + Replace the exact material id in the job item. +**item-type ** + Replace the exact item type id in the job item. diff --git a/docs/plugins/labormanager.rst b/docs/plugins/labormanager.rst new file mode 100644 index 000000000..d16a10e33 --- /dev/null +++ b/docs/plugins/labormanager.rst @@ -0,0 +1,105 @@ +labormanager +============ +Automatically manage dwarf labors to efficiently complete jobs. +Labormanager is derived from autolabor (above) but uses a completely +different approach to assigning jobs to dwarves. While autolabor tries +to keep as many dwarves busy as possible, labormanager instead strives +to get jobs done as quickly as possible. + +Labormanager frequently scans the current job list, current list of +dwarfs, and the map to determine how many dwarves need to be assigned to +what labors in order to meet all current labor needs without starving +any particular type of job. + +.. warning:: + + *As with autolabor, labormanager will override any manual changes you + make to labors while it is enabled, including through other tools such + as Dwarf Therapist* + +Simple usage: + +:enable labormanager: Enables the plugin with default settings. + (Persistent per fortress) + +:disable labormanager: Disables the plugin. + +Anything beyond this is optional - labormanager works fairly well on the +default settings. + +The default priorities for each labor vary (some labors are higher +priority by default than others). The way the plugin works is that, once +it determines how many of each labor is needed, it then sorts them by +adjusted priority. (Labors other than hauling have a bias added to them +based on how long it's been since they were last used, to prevent job +starvation.) The labor with the highest priority is selected, the "best +fit" dwarf for that labor is assigned to that labor, and then its +priority is *halved*. This process is repeated until either dwarfs or +labors run out. + +Because there is no easy way to detect how many haulers are actually +needed at any moment, the plugin always ensures that at least one dwarf +is assigned to each of the hauling labors, even if no hauling jobs are +detected. At least one dwarf is always assigned to construction removing +and cleaning because these jobs also cannot be easily detected. Lever +pulling is always assigned to everyone. Any dwarfs for which there are +no jobs will be assigned hauling, lever pulling, and cleaning labors. If +you use animal trainers, note that labormanager will misbehave if you +assign specific trainers to specific animals; results are only guaranteed +if you use "any trainer", and animal trainers will probably be +overallocated in any case. + +Labormanager also sometimes assigns extra labors to currently busy +dwarfs so that when they finish their current job, they will go off and +do something useful instead of standing around waiting for a job. + +There is special handling to ensure that at least one dwarf is assigned +to haul food whenever food is detected left in a place where it will rot +if not stored. This will cause a dwarf to go idle if you have no +storepiles to haul food to. + +Dwarfs who are unable to work (child, in the military, wounded, +handless, asleep, in a meeting) are entirely excluded from labor +assignment. Any dwarf explicitly assigned to a burrow will also be +completely ignored by labormanager. + +The fitness algorithm for assigning jobs to dwarfs generally attempts to +favor dwarfs who are more skilled over those who are less skilled. It +also tries to avoid assigning female dwarfs with children to jobs that +are "outside", favors assigning "outside" jobs to dwarfs who are +carrying a tool that could be used as a weapon, and tries to minimize +how often dwarfs have to reequip. + +Labormanager automatically determines medical needs and reserves health +care providers as needed. Note that this may cause idling if you have +injured dwarfs but no or inadequate hospital facilities. + +Hunting is never assigned without a butchery, and fishing is never +assigned without a fishery, and neither of these labors is assigned +unless specifically enabled. + +The method by which labormanager determines what labor is needed for a +particular job is complicated and, in places, incomplete. In some +situations, labormanager will detect that it cannot determine what labor +is required. It will, by default, pause and print an error message on +the dfhack console, followed by the message "LABORMANAGER: Game paused +so you can investigate the above message.". If this happens, please open +an issue on github, reporting the lines that immediately preceded this +message. You can tell labormanager to ignore this error and carry on by +typing ``labormanager pause-on-error no``, but be warned that some job may go +undone in this situation. + +Advanced usage: + +:labormanager enable: Turn plugin on. +:labormanager disable: Turn plugin off. +:labormanager priority : Set the priority value (see above) for labor to . +:labormanager reset : Reset the priority value of labor to its default. +:labormanager reset-all: Reset all priority values to their defaults. +:labormanager allow-fishing: Allow dwarfs to fish. *Warning* This tends to result in most of the fort going fishing. +:labormanager forbid-fishing: Forbid dwarfs from fishing. Default behavior. +:labormanager allow-hunting: Allow dwarfs to hunt. *Warning* This tends to result in as many dwarfs going hunting as you have crossbows. +:labormanager forbid-hunting: Forbid dwarfs from hunting. Default behavior. +:labormanager list: Show current priorities and current allocation stats. +:labormanager pause-on-error yes: Make labormanager pause if the labor inference engine fails. See above. +:labormanager pause-on-error no: Allow labormanager to continue past a labor inference engine failure. diff --git a/docs/plugins/lair.rst b/docs/plugins/lair.rst new file mode 100644 index 000000000..b80e71e3a --- /dev/null +++ b/docs/plugins/lair.rst @@ -0,0 +1,12 @@ +lair +==== +This command allows you to mark the map as a monster lair, preventing item +scatter on abandon. When invoked as ``lair reset``, it does the opposite. + +Unlike `reveal`, this command doesn't save the information about tiles - you +won't be able to restore state of real monster lairs using ``lair reset``. + +Options: + +:lair: Mark the map as monster lair +:lair reset: Mark the map as ordinary (not lair) diff --git a/docs/plugins/liquids-here.rst b/docs/plugins/liquids-here.rst new file mode 100644 index 000000000..d838170a2 --- /dev/null +++ b/docs/plugins/liquids-here.rst @@ -0,0 +1,6 @@ +liquids-here +------------ +Run the liquid spawner with the current/last settings made in liquids (if no +settings in liquids were made it paints a point of 7/7 magma by default). + +Intended to be used as keybinding. Requires an active in-game cursor. diff --git a/docs/plugins/liquids.rst b/docs/plugins/liquids.rst new file mode 100644 index 000000000..d55962ad5 --- /dev/null +++ b/docs/plugins/liquids.rst @@ -0,0 +1,65 @@ +liquids +======= +Allows adding magma, water and obsidian to the game. It replaces the normal +dfhack command line and can't be used from a hotkey. Settings will be remembered +as long as dfhack runs. Intended for use in combination with the command +``liquids-here`` (which can be bound to a hotkey). See also :issue:`80`. + +.. warning:: + + Spawning and deleting liquids can mess up pathing data and + temperatures (creating heat traps). You've been warned. + +.. note:: + + `gui/liquids` is an in-game UI for this script. + +Settings will be remembered until you quit DF. You can call `liquids-here` to execute +the last configured action, which is useful in combination with keybindings. + +Usage: point the DF cursor at a tile you want to modify and use the commands. + +If you only want to add or remove water or magma from one tile, +`source` may be easier to use. + +Commands +-------- +Misc commands: + +:q: quit +:help, ?: print this list of commands +:: put liquid + +Modes: + +:m: switch to magma +:w: switch to water +:o: make obsidian wall instead +:of: make obsidian floors +:rs: make a river source +:f: flow bits only +:wclean: remove salt and stagnant flags from tiles + +Set-Modes and flow properties (only for magma/water): + +:s+: only add mode +:s.: set mode +:s-: only remove mode +:f+: make the spawned liquid flow +:f.: don't change flow state (read state in flow mode) +:f-: make the spawned liquid static + +Permaflow (only for water): + +:pf.: don't change permaflow state +:pf-: make the spawned liquid static +:pf[NS][EW]: make the spawned liquid permanently flow +:0-7: set liquid amount + +Brush size and shape: + +:p, point: Single tile +:r, range: Block with cursor at bottom north-west (any place, any size) +:block: DF map block with cursor in it (regular spaced 16x16x1 blocks) +:column: Column from cursor, up through free space +:flood: Flood-fill water tiles from cursor (only makes sense with wclean) diff --git a/docs/plugins/manipulator.rst b/docs/plugins/manipulator.rst new file mode 100644 index 000000000..024438d85 --- /dev/null +++ b/docs/plugins/manipulator.rst @@ -0,0 +1,182 @@ +manipulator +=========== +An in-game equivalent to the popular program Dwarf Therapist. + +To activate, open the unit screen and press :kbd:`l`. + +.. image:: ../images/manipulator.png + +The far left column displays the unit's Happiness (color-coded based on its +value), Name, Profession/Squad, and the right half of the screen displays each +dwarf's labor settings and skill levels (0-9 for Dabbling through Professional, +A-E for Great through Grand Master, and U-Z for Legendary through Legendary+5). + +Cells with teal backgrounds denote skills not controlled by labors, e.g. +military and social skills. + +.. image:: ../images/manipulator2.png + +Press :kbd:`t` to toggle between Profession, Squad, and Job views. + +.. image:: ../images/manipulator3.png + +Use the arrow keys or number pad to move the cursor around, holding :kbd:`Shift` to +move 10 tiles at a time. + +Press the Z-Up (:kbd:`<`) and Z-Down (:kbd:`>`) keys to move quickly between labor/skill +categories. The numpad Z-Up and Z-Down keys seek to the first or last unit +in the list. :kbd:`Backspace` seeks to the top left corner. + +Press Enter to toggle the selected labor for the selected unit, or Shift+Enter +to toggle all labors within the selected category. + +Press the :kbd:`+`:kbd:`-` keys to sort the unit list according to the currently selected +skill/labor, and press the :kbd:`*`:kbd:`/` keys to sort the unit list by Name, Profession/Squad, +Happiness, or Arrival order (using :kbd:`Tab` to select which sort method to use here). + +With a unit selected, you can press the :kbd:`v` key to view its properties (and +possibly set a custom nickname or profession) or the :kbd:`c` key to exit +Manipulator and zoom to its position within your fortress. + +The following mouse shortcuts are also available: + +* Click on a column header to sort the unit list. Left-click to sort it in one + direction (descending for happiness or labors/skills, ascending for name, + profession or squad) and right-click to sort it in the opposite direction. +* Left-click on a labor cell to toggle that labor. Right-click to move the + cursor onto that cell instead of toggling it. +* Left-click on a unit's name, profession or squad to view its properties. +* Right-click on a unit's name, profession or squad to zoom to it. + +Pressing :kbd:`Esc` normally returns to the unit screen, but :kbd:`Shift`:kbd:`Esc` would exit +directly to the main dwarf mode screen. + +Professions +----------- + +The manipulator plugin supports saving professions: a named set of labors that can be +quickly applied to one or multiple dwarves. + +To save a profession, highlight a dwarf and press :kbd:`P`. The profession will be saved using +the custom profession name of the dwarf, or the default for that dwarf if no custom profession +name has been set. + +To apply a profession, either highlight a single dwarf or select multiple with +:kbd:`x`, and press :kbd:`p` to select the profession to apply. All labors for +the selected dwarves will be reset to the labors of the chosen profession. + +Professions are saved as human-readable text files in the +``dfhack-config/professions`` folder within the DF folder, and can be edited or +deleted there. + +The professions library +~~~~~~~~~~~~~~~~~~~~~~~ + +The manipulator plugin comes with a library of professions that you can assign +to your dwarves. + +If you'd rather use Dwarf Therapist to manage your labors, it is easy to import +these professions to DT and use them there. Simply assign the professions you +want to import to a dwarf. Once you have assigned a profession to at least one +dwarf, you can select "Import Professions from DF" in the DT "File" menu. The +professions will then be available for use in DT. + +In the charts below, the "At Start" and "Max" columns indicate the approximate +number of dwarves of each profession that you are likely to need at the start of +the game and how many you are likely to need in a mature fort. These are just +approximations. Your playstyle may demand more or fewer of each profession. + +============= ======== ===== ================================================= +Profession At Start Max Description +============= ======== ===== ================================================= +Chef 0 3 Buchery, Tanning, and Cooking. It is important to + focus just a few dwarves on cooking since + well-crafted meals make dwarves very happy. They + are also an excellent trade good. +Craftsdwarf 0 4-6 All labors used at Craftsdwarf's workshops, + Glassmaker's workshops, and kilns. +Doctor 0 2-4 The full suite of medical labors, plus Animal + Caretaking for those using the dwarfvet plugin. +Farmer 1 4 Food- and animal product-related labors. This + profession also has the ``Alchemist`` labor + enabled since they need to focus on food-related + jobs, though you might want to disable + ``Alchemist`` for your first farmer until there + are actual farming duties to perform. +Fisherdwarf 0 0-1 Fishing and fish cleaning. If you assign this + profession to any dwarf, be prepared to be + inundated with fish. Fisherdwarves *never stop + fishing*. Be sure to also run ``prioritize -a + PrepareRawFish ExtractFromRawFish`` or else + caught fish will just be left to rot. +Hauler 0 >20 All hauling labors plus Siege Operating, Mechanic + (so haulers can assist in reloading traps) and + Architecture (so haulers can help build massive + windmill farms and pump stacks). As you + accumulate enough Haulers, you can turn off + hauling labors for other dwarves so they can + focus on their skilled tasks. You may also want + to restrict your Mechanic's workshops to only + skilled mechanics so your haulers don't make + low-quality mechanisms. +Laborer 0 10-12 All labors that don't improve quality with skill, + such as Soapmaking and furnace labors. +Marksdwarf 0 10-30 Similar to Hauler. See the description for + Meleedwarf below for more details. +Mason 2 2-4 Masonry and Gem Cutting/Encrusting. In the early + game, you may need to run "`prioritize` + ConstructBuilding" to get your masons to build + wells and bridges if they are too busy crafting + stone furniture. +Meleedwarf 0 20-50 Similar to Hauler, but without most civilian + labors. This profession is separate from Hauler + so you can find your military dwarves easily. + Meleedwarves and Marksdwarves have Mechanics and + hauling labors enabled so you can temporarily + deactivate your military after sieges and allow + your military dwarves to help clean up. +Migrant 0 0 You can assign this profession to new migrants + temporarily while you sort them into professions. + Like Marksdwarf and Meleedwarf, the purpose of + this profession is so you can find your new + dwarves more easily. +Miner 2 2-10 Mining and Engraving. This profession also has + the ``Alchemist`` labor enabled, which disables + hauling for those using the `autohauler` plugin. + Once the need for Miners tapers off in the late + game, dwarves with this profession make good + military dwarves, wielding their picks as + weapons. +Outdoorsdwarf 1 2-4 Carpentry, Bowyery, Woodcutting, Animal Training, + Trapping, Plant Gathering, Beekeeping, and Siege + Engineering. +Smith 0 2-4 Smithing labors. You may want to specialize your + Smiths to focus on a single smithing skill to + maximize equipment quality. +StartManager 1 0 All skills not covered by the other starting + professions (Miner, Mason, Outdoorsdwarf, and + Farmer), plus a few overlapping skills to + assist in critical tasks at the beginning of the + game. Individual labors should be turned off as + migrants are assigned more specialized + professions that cover them, and the StartManager + dwarf can eventually convert to some other + profession. +Tailor 0 2 Textile industry labors: Dying, Leatherworking, + Weaving, and Clothesmaking. +============= ======== ===== ================================================= + +A note on autohauler +~~~~~~~~~~~~~~~~~~~~ + +These profession definitions are designed to work well with or without the +`autohauler` plugin (which helps to keep your dwarves focused on skilled labors +instead of constantly being distracted by hauling). If you do want to use +autohauler, adding the following lines to your ``onMapLoad.init`` file will +configure it to let the professions manage the "Feed water to civilians" and +"Recover wounded" labors instead of enabling those labors for all hauling +dwarves:: + + on-new-fortress enable autohauler + on-new-fortress autohauler FEED_WATER_CIVILIANS allow + on-new-fortress autohauler RECOVER_WOUNDED allow diff --git a/docs/plugins/misery.rst b/docs/plugins/misery.rst new file mode 100644 index 000000000..1c146a10f --- /dev/null +++ b/docs/plugins/misery.rst @@ -0,0 +1,14 @@ +misery +====== +When enabled, fake bad thoughts will be added to all dwarves. + +Usage: + +:misery enable n: enable misery with optional magnitude n. If specified, n must + be positive. +:misery n: same as "misery enable n" +:misery enable: same as "misery enable 1" +:misery disable: stop adding new negative thoughts. This will not remove + existing negative thoughts. Equivalent to "misery 0". +:misery clear: remove fake thoughts, even after saving and reloading. Does + not change factor. diff --git a/docs/plugins/mode.rst b/docs/plugins/mode.rst new file mode 100644 index 000000000..9b3fc6984 --- /dev/null +++ b/docs/plugins/mode.rst @@ -0,0 +1,21 @@ +mode +==== +This command lets you see and change the game mode directly. + +.. warning:: + + Only use ``mode`` after making a backup of your save! + + Not all combinations are good for every situation and most of them will + produce undesirable results. There are a few good ones though. + +Examples: + + * You are in fort game mode, managing your fortress and paused. + * You switch to the arena game mode, *assume control of a creature* and then + * switch to adventure game mode(1). + You just lost a fortress and gained an adventurer. Alternatively: + + * You are in fort game mode, managing your fortress and paused at the esc menu. + * You switch to the adventure game mode, assume control of a creature, then save or retire. + * You just created a returnable mountain home and gained an adventurer. diff --git a/docs/plugins/mousequery.rst b/docs/plugins/mousequery.rst new file mode 100644 index 000000000..2f613acaa --- /dev/null +++ b/docs/plugins/mousequery.rst @@ -0,0 +1,16 @@ +mousequery +========== +Adds mouse controls to the DF interface, e.g. click-and-drag designations. + +Options: + +:plugin: enable/disable the entire plugin +:rbutton: enable/disable right mouse button +:track: enable/disable moving cursor in build and designation mode +:edge: enable/disable active edge scrolling (when on, will also enable tracking) +:live: enable/disable query view when unpaused +:delay: Set delay when edge scrolling in tracking mode. Omit amount to display current setting. + +Usage:: + + mousequery [plugin] [rbutton] [track] [edge] [live] [enable|disable] diff --git a/docs/plugins/nestboxes.rst b/docs/plugins/nestboxes.rst new file mode 100644 index 000000000..2b633f31b --- /dev/null +++ b/docs/plugins/nestboxes.rst @@ -0,0 +1,5 @@ +nestboxes +========= + +Automatically scan for and forbid fertile eggs incubating in a nestbox. +Toggle status with `enable` or `disable `. diff --git a/docs/plugins/nopause.rst b/docs/plugins/nopause.rst new file mode 100644 index 000000000..c30543187 --- /dev/null +++ b/docs/plugins/nopause.rst @@ -0,0 +1,4 @@ +nopause +======= +Disables pausing (both manual and automatic) with the exception of pause forced +by `reveal` ``hell``. This is nice for digging under rivers. diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst new file mode 100644 index 000000000..eb0e8a7ca --- /dev/null +++ b/docs/plugins/orders.rst @@ -0,0 +1,116 @@ +orders +====== + +A plugin for manipulating manager orders. + +Subcommands: + +:list: Shows the list of previously exported orders, including the orders library. +:export NAME: Exports the current list of manager orders to a file named ``dfhack-config/orders/NAME.json``. +:import NAME: Imports manager orders from a file named ``dfhack-config/orders/NAME.json``. +:clear: Deletes all manager orders in the current embark. +:sort: Sorts current manager orders by repeat frequency so daily orders don't + prevent other orders from ever being completed: one-time orders first, then + yearly, seasonally, monthly, then finally daily. + +You can keep your orders automatically sorted by adding the following command to +your ``onMapLoad.init`` file:: + + repeat -name orders-sort -time 1 -timeUnits days -command [ orders sort ] + + +The orders library +------------------ + +DFHack comes with a library of useful manager orders that are ready for import: + +:source:`basic.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection of orders handles basic fort necessities: + +- prepared meals and food products (and by-products like oil) +- booze/mead +- thread/cloth/dye +- pots/jugs/buckets/mugs +- bags of leather, cloth, silk, and yarn +- crafts and totems from otherwise unusable by-products +- mechanisms/cages +- splints/crutches +- lye/soap +- ash/potash +- beds/wheelbarrows/minecarts +- scrolls + +You should import it as soon as you have enough dwarves to perform the tasks. +Right after the first migration wave is usually a good time. + +:source:`furnace.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection creates basic items that require heat. It is separated out from +``basic.json`` to give players the opportunity to set up magma furnaces first in +order to save resources. It handles: + +- charcoal (including smelting of bituminous coal and lignite) +- pearlash +- sand +- green/clear/crystal glass +- adamantine processing +- item melting + +Orders are missing for plaster powder until DF :bug:`11803` is fixed. + +:source:`military.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection adds high-volume smelting jobs for military-grade metal ores and +produces weapons and armor: + +- leather backpacks/waterskins/cloaks/quivers/armor +- bone/wooden bolts +- smelting for platinum, silver, steel, bronze, bismuth bronze, and copper (and + their dependencies) +- bronze/bismuth bronze/copper bolts +- platinum/silver/steel/iron/bismuth bronze/bronze/copper weapons and armor, + with checks to ensure only the best available materials are being used + +If you set a stockpile to take weapons and armor of less than masterwork quality +and turn on `automelt` (like what `dreamfort` provides on its industry level), +these orders will automatically upgrade your military equipment to masterwork. +Make sure you have a lot of fuel (or magma forges and furnaces) before you turn +``automelt`` on, though! + +This file should only be imported, of course, if you need to equip a military. + +:source:`smelting.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection adds smelting jobs for all ores. It includes handling the ores +already managed by ``military.json``, but has lower limits. This ensures all +ores will be covered if a player imports ``smelting`` but not ``military``, but +the higher-volume ``military`` orders will take priority if both are imported. + +:source:`rockstock.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection of orders keeps a small stock of all types of rock furniture. +This allows you to do ad-hoc furnishings of guildhalls, libraries, temples, or +other rooms with `buildingplan` and your masons will make sure there is always +stock on hand to fulfill the plans. + +:source:`glassstock.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Similar to ``rockstock`` above, this collection keeps a small stock of all types +of glass furniture. If you have a functioning glass industry, this is more +sustainable than ``rockstock`` since you can never run out of sand. If you have +plenty of rock and just want the variety, you can import both ``rockstock`` and +``glassstock`` to get a mixture of rock and glass furnishings in your fort. + +There are a few items that ``glassstock`` produces that ``rockstock`` does not, +since there are some items that can not be made out of rock, for example: + +- tubes and corkscrews for building magma-safe screw pumps +- windows +- terrariums (as an alternative to wooden cages) diff --git a/docs/plugins/petcapRemover.rst b/docs/plugins/petcapRemover.rst new file mode 100644 index 000000000..65ebaeeab --- /dev/null +++ b/docs/plugins/petcapRemover.rst @@ -0,0 +1,19 @@ +petcapRemover +============= +Allows you to remove or raise the pet population cap. In vanilla +DF, pets will not reproduce unless the population is below 50 and the number of +children of that species is below a certain percentage. This plugin allows +removing the second restriction and removing or raising the first. Pets still +require PET or PET_EXOTIC tags in order to reproduce. Type ``help petcapRemover`` +for exact usage. In order to make population more stable and avoid sudden +population booms as you go below the raised population cap, this plugin counts +pregnancies toward the new population cap. It can still go over, but only in the +case of multiple births. + +Usage: + +:petcapRemover: cause pregnancies now and schedule the next check +:petcapRemover every n: set how often in ticks the plugin checks for possible pregnancies +:petcapRemover cap n: set the new cap to n. if n = 0, no cap +:petcapRemover pregtime n: sets the pregnancy duration to n ticks. natural pregnancies are + 300000 ticks for the current race and 200000 for everyone else diff --git a/docs/plugins/plant.rst b/docs/plugins/plant.rst new file mode 100644 index 000000000..b621b890d --- /dev/null +++ b/docs/plugins/plant.rst @@ -0,0 +1,16 @@ +plant +===== +A tool for creating shrubs, growing, or getting rid of them. + +Subcommands: + +:create: Creates a new sapling under the cursor. Takes a raw ID as argument + (e.g. TOWER_CAP). The cursor must be located on a dirt or grass floor tile. +:grow: Turns saplings into trees; under the cursor if a sapling is selected, + or every sapling on the map if the cursor is hidden. + +For mass effects, use one of the additional options: + +:shrubs: affect all shrubs on the map +:trees: affect all trees on the map +:all: affect every plant! diff --git a/docs/plugins/power-meter.rst b/docs/plugins/power-meter.rst new file mode 100644 index 000000000..163ef91be --- /dev/null +++ b/docs/plugins/power-meter.rst @@ -0,0 +1,6 @@ +power-meter +=========== +The power-meter plugin implements a modified pressure plate that detects power being +supplied to gear boxes built in the four adjacent N/S/W/E tiles. + +The configuration front-end is implemented by `gui/power-meter`. diff --git a/docs/plugins/probe.rst b/docs/plugins/probe.rst new file mode 100644 index 000000000..1dcc28336 --- /dev/null +++ b/docs/plugins/probe.rst @@ -0,0 +1,13 @@ +probe +===== + +This plugin provides multiple commands that print low-level properties of the +selected objects. + +* ``probe``: prints some properties of the tile selected with :kbd:`k`. Some of + these properties can be passed into `tiletypes`. +* ``cprobe``: prints some properties of the unit selected with :kbd:`v`, as well + as the IDs of any worn items. `gui/gm-unit` and `gui/gm-editor` are more + complete in-game alternatives. +* ``bprobe``: prints some properties of the building selected with :kbd:`q` or + :kbd:`t`. `gui/gm-editor` is a more complete in-game alternative. diff --git a/docs/plugins/prospect.rst b/docs/plugins/prospect.rst new file mode 100644 index 000000000..a8979f1cc --- /dev/null +++ b/docs/plugins/prospect.rst @@ -0,0 +1,51 @@ +prospect +======== + +**Usage:** + + ``prospect [all|hell] []`` + +Shows a summary of resources that exist on the map. By default, only the visible +part of the map is scanned. Include the ``all`` keyword if you want ``prospect`` +to scan the whole map as if it were revealed. Use ``hell`` instead of ``all`` if +you also want to see the Z range of HFS tubes in the 'features' report section. + +**Options:** + +:``-h``, ``--help``: + Shows this help text. +:``-s``, ``--show ``: + Shows only the named comma-separated list of report sections. Report section + names are: summary, liquids, layers, features, ores, gems, veins, shrubs, + and trees. If run during pre-embark, only the layers, ores, gems, and veins + report sections are available. +:``-v``, ``--values``: + Includes material value in the output. Most useful for the 'gems' report + section. + +**Examples:** + +``prospect all`` + Shows the entire report for the entire map. + +``prospect hell --show layers,ores,veins`` + Shows only the layers, ores, and other vein stone report sections, and + includes information on HFS tubes when a fort is loaded. + +``prospect all -sores`` + Show only information about ores for the pre-embark or fortress map report. + +**Pre-embark estimate:** + +If prospect is called during the embark selection screen, it displays an +estimate of layer stone availability. If the ``all`` keyword is specified, it +also estimates ores, gems, and vein material. The estimate covers all tiles of +the embark rectangle. + +.. note:: + + The results of pre-embark prospect are an *estimate*, and can at best be + expected to be somewhere within +/- 30% of the true amount; sometimes it + does a lot worse. Especially, it is not clear how to precisely compute how + many soil layers there will be in a given embark tile, so it can report a + whole extra layer, or omit one that is actually present. diff --git a/docs/plugins/prospector.rst b/docs/plugins/prospector.rst new file mode 100644 index 000000000..a8979f1cc --- /dev/null +++ b/docs/plugins/prospector.rst @@ -0,0 +1,51 @@ +prospect +======== + +**Usage:** + + ``prospect [all|hell] []`` + +Shows a summary of resources that exist on the map. By default, only the visible +part of the map is scanned. Include the ``all`` keyword if you want ``prospect`` +to scan the whole map as if it were revealed. Use ``hell`` instead of ``all`` if +you also want to see the Z range of HFS tubes in the 'features' report section. + +**Options:** + +:``-h``, ``--help``: + Shows this help text. +:``-s``, ``--show ``: + Shows only the named comma-separated list of report sections. Report section + names are: summary, liquids, layers, features, ores, gems, veins, shrubs, + and trees. If run during pre-embark, only the layers, ores, gems, and veins + report sections are available. +:``-v``, ``--values``: + Includes material value in the output. Most useful for the 'gems' report + section. + +**Examples:** + +``prospect all`` + Shows the entire report for the entire map. + +``prospect hell --show layers,ores,veins`` + Shows only the layers, ores, and other vein stone report sections, and + includes information on HFS tubes when a fort is loaded. + +``prospect all -sores`` + Show only information about ores for the pre-embark or fortress map report. + +**Pre-embark estimate:** + +If prospect is called during the embark selection screen, it displays an +estimate of layer stone availability. If the ``all`` keyword is specified, it +also estimates ores, gems, and vein material. The estimate covers all tiles of +the embark rectangle. + +.. note:: + + The results of pre-embark prospect are an *estimate*, and can at best be + expected to be somewhere within +/- 30% of the true amount; sometimes it + does a lot worse. Especially, it is not clear how to precisely compute how + many soil layers there will be in a given embark tile, so it can report a + whole extra layer, or omit one that is actually present. diff --git a/docs/plugins/rb.rst b/docs/plugins/rb.rst new file mode 100644 index 000000000..359d6149b --- /dev/null +++ b/docs/plugins/rb.rst @@ -0,0 +1,4 @@ +ruby +==== +Ruby language plugin, which evaluates the following arguments as a ruby string. +Best used as ``:rb [string]``, for the special parsing mode. Alias ``rb_eval``. diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst new file mode 100644 index 000000000..f3d00d8e1 --- /dev/null +++ b/docs/plugins/regrass.rst @@ -0,0 +1,3 @@ +regrass +======= +Regrows all the grass. Not much to it ;) diff --git a/docs/plugins/remotefortressreader.rst b/docs/plugins/remotefortressreader.rst new file mode 100644 index 000000000..287debca8 --- /dev/null +++ b/docs/plugins/remotefortressreader.rst @@ -0,0 +1,4 @@ +remotefortressreader +==================== +An in-development plugin for realtime fortress visualisation. +See :forums:`Armok Vision <146473>`. diff --git a/docs/plugins/rename.rst b/docs/plugins/rename.rst new file mode 100644 index 000000000..55534e636 --- /dev/null +++ b/docs/plugins/rename.rst @@ -0,0 +1,19 @@ +rename +====== +Allows renaming various things. Use `gui/rename` for an in-game interface. + +Options: + +``rename squad "name"`` + Rename squad by index to 'name'. +``rename hotkey \"name\"`` + Rename hotkey by index. This allows assigning + longer commands to the DF hotkeys. +``rename unit "nickname"`` + Rename a unit/creature highlighted in the DF user interface. +``rename unit-profession "custom profession"`` + Change proffession name of the highlighted unit/creature. +``rename building "name"`` + Set a custom name for the selected building. + The building must be one of stockpile, workshop, furnace, trap, + siege engine or an activity zone. diff --git a/docs/plugins/rendermax.rst b/docs/plugins/rendermax.rst new file mode 100644 index 000000000..a173a75db --- /dev/null +++ b/docs/plugins/rendermax.rst @@ -0,0 +1,18 @@ +rendermax +========= +A collection of renderer replacing/enhancing filters. For better effect try changing the +black color in palette to non totally black. See :forums:`128487` for more info. + +Options: + +:trippy: Randomizes the color of each tiles. Used for fun, or testing. +:light: Enable lighting engine. +:light reload: Reload the settings file. +:light sun |cycle: Set time to (in hours) or set it to df time cycle. +:occlusionON, occlusionOFF: Show debug occlusion info. +:disable: Disable any filter that is enabled. + +An image showing lava and dragon breath. Not pictured here: sunlight, shining items/plants, +materials that color the light etc... + +.. image:: ../images/rendermax.png diff --git a/docs/plugins/restrictice.rst b/docs/plugins/restrictice.rst new file mode 100644 index 000000000..558fb5679 --- /dev/null +++ b/docs/plugins/restrictice.rst @@ -0,0 +1,4 @@ +restrictice +=========== +Restrict traffic on all tiles on top of visible ice. +See also `alltraffic`, `filltraffic`, and `restrictliquids`. diff --git a/docs/plugins/restrictliquids.rst b/docs/plugins/restrictliquids.rst new file mode 100644 index 000000000..4f6cd9550 --- /dev/null +++ b/docs/plugins/restrictliquids.rst @@ -0,0 +1,4 @@ +restrictliquids +=============== +Restrict traffic on all visible tiles with liquid. +See also `alltraffic`, `filltraffic`, and `restrictice`. diff --git a/docs/plugins/resume.rst b/docs/plugins/resume.rst new file mode 100644 index 000000000..ccca39e28 --- /dev/null +++ b/docs/plugins/resume.rst @@ -0,0 +1,4 @@ +resume +====== +Allows automatic resumption of suspended constructions, along with colored +UI hints for construction status. diff --git a/docs/plugins/reveal.rst b/docs/plugins/reveal.rst new file mode 100644 index 000000000..da7be8277 --- /dev/null +++ b/docs/plugins/reveal.rst @@ -0,0 +1,23 @@ +reveal +====== +This reveals the map. By default, HFS will remain hidden so that the demons +don't spawn. You can use ``reveal hell`` to reveal everything. With hell revealed, +you won't be able to unpause until you hide the map again. If you really want +to unpause with hell revealed, use ``reveal demons``. + +Reveal also works in adventure mode, but any of its effects are negated once +you move. When you use it this way, you don't need to run ``unreveal``. + +Usage and related commands: + +:reveal: Reveal the whole map, except for HFS to avoid demons spawning +:reveal hell: Also show hell, but requires ``unreveal`` before unpausing +:reveal demon: Reveals everything and allows unpausing - good luck! +:unreveal: Reverts the effects of ``reveal`` +:revtoggle: Switches between ``reveal`` and ``unreveal`` +:revflood: Hide everything, then reveal tiles with a path to the cursor. + Note that tiles behind constructed walls are also revealed as a + workaround for :bug:`1871`. +:revforget: Discard info about what was visible before revealing the map. + Only useful where (e.g.) you abandoned with the fort revealed + and no longer want the data. diff --git a/docs/plugins/revflood.rst b/docs/plugins/revflood.rst new file mode 100644 index 000000000..da7be8277 --- /dev/null +++ b/docs/plugins/revflood.rst @@ -0,0 +1,23 @@ +reveal +====== +This reveals the map. By default, HFS will remain hidden so that the demons +don't spawn. You can use ``reveal hell`` to reveal everything. With hell revealed, +you won't be able to unpause until you hide the map again. If you really want +to unpause with hell revealed, use ``reveal demons``. + +Reveal also works in adventure mode, but any of its effects are negated once +you move. When you use it this way, you don't need to run ``unreveal``. + +Usage and related commands: + +:reveal: Reveal the whole map, except for HFS to avoid demons spawning +:reveal hell: Also show hell, but requires ``unreveal`` before unpausing +:reveal demon: Reveals everything and allows unpausing - good luck! +:unreveal: Reverts the effects of ``reveal`` +:revtoggle: Switches between ``reveal`` and ``unreveal`` +:revflood: Hide everything, then reveal tiles with a path to the cursor. + Note that tiles behind constructed walls are also revealed as a + workaround for :bug:`1871`. +:revforget: Discard info about what was visible before revealing the map. + Only useful where (e.g.) you abandoned with the fort revealed + and no longer want the data. diff --git a/docs/plugins/revforget.rst b/docs/plugins/revforget.rst new file mode 100644 index 000000000..da7be8277 --- /dev/null +++ b/docs/plugins/revforget.rst @@ -0,0 +1,23 @@ +reveal +====== +This reveals the map. By default, HFS will remain hidden so that the demons +don't spawn. You can use ``reveal hell`` to reveal everything. With hell revealed, +you won't be able to unpause until you hide the map again. If you really want +to unpause with hell revealed, use ``reveal demons``. + +Reveal also works in adventure mode, but any of its effects are negated once +you move. When you use it this way, you don't need to run ``unreveal``. + +Usage and related commands: + +:reveal: Reveal the whole map, except for HFS to avoid demons spawning +:reveal hell: Also show hell, but requires ``unreveal`` before unpausing +:reveal demon: Reveals everything and allows unpausing - good luck! +:unreveal: Reverts the effects of ``reveal`` +:revtoggle: Switches between ``reveal`` and ``unreveal`` +:revflood: Hide everything, then reveal tiles with a path to the cursor. + Note that tiles behind constructed walls are also revealed as a + workaround for :bug:`1871`. +:revforget: Discard info about what was visible before revealing the map. + Only useful where (e.g.) you abandoned with the fort revealed + and no longer want the data. diff --git a/docs/plugins/revtoggle.rst b/docs/plugins/revtoggle.rst new file mode 100644 index 000000000..da7be8277 --- /dev/null +++ b/docs/plugins/revtoggle.rst @@ -0,0 +1,23 @@ +reveal +====== +This reveals the map. By default, HFS will remain hidden so that the demons +don't spawn. You can use ``reveal hell`` to reveal everything. With hell revealed, +you won't be able to unpause until you hide the map again. If you really want +to unpause with hell revealed, use ``reveal demons``. + +Reveal also works in adventure mode, but any of its effects are negated once +you move. When you use it this way, you don't need to run ``unreveal``. + +Usage and related commands: + +:reveal: Reveal the whole map, except for HFS to avoid demons spawning +:reveal hell: Also show hell, but requires ``unreveal`` before unpausing +:reveal demon: Reveals everything and allows unpausing - good luck! +:unreveal: Reverts the effects of ``reveal`` +:revtoggle: Switches between ``reveal`` and ``unreveal`` +:revflood: Hide everything, then reveal tiles with a path to the cursor. + Note that tiles behind constructed walls are also revealed as a + workaround for :bug:`1871`. +:revforget: Discard info about what was visible before revealing the map. + Only useful where (e.g.) you abandoned with the fort revealed + and no longer want the data. diff --git a/docs/plugins/ruby.rst b/docs/plugins/ruby.rst new file mode 100644 index 000000000..359d6149b --- /dev/null +++ b/docs/plugins/ruby.rst @@ -0,0 +1,4 @@ +ruby +==== +Ruby language plugin, which evaluates the following arguments as a ruby string. +Best used as ``:rb [string]``, for the special parsing mode. Alias ``rb_eval``. diff --git a/docs/plugins/search.rst b/docs/plugins/search.rst new file mode 100644 index 000000000..57b9e5df4 --- /dev/null +++ b/docs/plugins/search.rst @@ -0,0 +1,40 @@ + +.. _search-plugin: + +search +====== +The search plugin adds search to the Stocks, Animals, Trading, Stockpile, +Noble (assignment candidates), Military (position candidates), Burrows +(unit list), Rooms, Announcements, Job List and Unit List screens. + +.. image:: ../images/search.png + +Searching works the same way as the search option in :guilabel:`Move to Depot`. +You will see the Search option displayed on screen with a hotkey (usually :kbd:`s`). +Pressing it lets you start typing a query and the relevant list will start +filtering automatically. + +Pressing :kbd:`Enter`, :kbd:`Esc` or the arrow keys will return you to browsing the now +filtered list, which still functions as normal. You can clear the filter +by either going back into search mode and backspacing to delete it, or +pressing the "shifted" version of the search hotkey while browsing the +list (e.g. if the hotkey is :kbd:`s`, then hitting :kbd:`Shift`:kbd:`s` will clear any +filter). + +Leaving any screen automatically clears the filter. + +In the Trade screen, the actual trade will always only act on items that +are actually visible in the list; the same effect applies to the Trade +Value numbers displayed by the screen. Because of this, the :kbd:`t` key is +blocked while search is active, so you have to reset the filters first. +Pressing :kbd:`Alt`:kbd:`C` will clear both search strings. + +In the stockpile screen the option only appears if the cursor is in the +rightmost list: + +.. image:: ../images/search-stockpile.png + +Note that the 'Permit XXX'/'Forbid XXX' keys conveniently operate only +on items actually shown in the rightmost list, so it is possible to select +only fat or tallow by forbidding fats, then searching for fat/tallow, and +using Permit Fats again while the list is filtered. diff --git a/docs/plugins/seedwatch.rst b/docs/plugins/seedwatch.rst new file mode 100644 index 000000000..1d5ab9e62 --- /dev/null +++ b/docs/plugins/seedwatch.rst @@ -0,0 +1,27 @@ +seedwatch +========= +Watches the numbers of seeds available and enables/disables seed and plant cooking. + +Each plant type can be assigned a limit. If their number falls below that limit, +the plants and seeds of that type will be excluded from cookery. +If the number rises above the limit + 20, then cooking will be allowed. + +The plugin needs a fortress to be loaded and will deactivate automatically otherwise. +You have to reactivate with 'seedwatch start' after you load the game. + +Options: + +:all: Adds all plants from the abbreviation list to the watch list. +:start: Start watching. +:stop: Stop watching. +:info: Display whether seedwatch is watching, and the watch list. +:clear: Clears the watch list. + +Examples: + +``seedwatch MUSHROOM_HELMET_PLUMP 30`` + add ``MUSHROOM_HELMET_PLUMP`` to the watch list, limit = 30 +``seedwatch MUSHROOM_HELMET_PLUMP`` + removes ``MUSHROOM_HELMET_PLUMP`` from the watch list. +``seedwatch all 30`` + adds all plants from the abbreviation list to the watch list, the limit being 30. diff --git a/docs/plugins/showmood.rst b/docs/plugins/showmood.rst new file mode 100644 index 000000000..9276766c4 --- /dev/null +++ b/docs/plugins/showmood.rst @@ -0,0 +1,3 @@ +showmood +======== +Shows all items needed for the currently active strange mood. diff --git a/docs/plugins/siege-engine.rst b/docs/plugins/siege-engine.rst new file mode 100644 index 000000000..0e9511d3d --- /dev/null +++ b/docs/plugins/siege-engine.rst @@ -0,0 +1,12 @@ +siege-engine +============ +Siege engines in DF haven't been updated since the game was 2D, and can +only aim in four directions. To make them useful above-ground, +this plugin allows you to: + +* link siege engines to stockpiles +* restrict operator skill levels (like workshops) +* load any object into a catapult, not just stones +* aim at a rectangular area in any direction, and across Z-levels + +The front-end is implemented by `gui/siege-engine`. diff --git a/docs/plugins/sort-items.rst b/docs/plugins/sort-items.rst new file mode 100644 index 000000000..d18f33c4e --- /dev/null +++ b/docs/plugins/sort-items.rst @@ -0,0 +1,17 @@ +.. _sort: + +sort-items +========== +Sort the visible item list:: + + sort-items order [order...] + +Sort the item list using the given sequence of comparisons. +The ``<`` prefix for an order makes undefined values sort first. +The ``>`` prefix reverses the sort order for defined values. + +Item order examples:: + + description material wear type quality + +The orderings are defined in ``hack/lua/plugins/sort/*.lua`` diff --git a/docs/plugins/sort-units.rst b/docs/plugins/sort-units.rst new file mode 100644 index 000000000..1a88381ce --- /dev/null +++ b/docs/plugins/sort-units.rst @@ -0,0 +1,17 @@ +sort-units +========== +Sort the visible unit list:: + + sort-units order [order...] + +Sort the unit list using the given sequence of comparisons. +The ``<`` prefix for an order makes undefined values sort first. +The ``>`` prefix reverses the sort order for defined values. + +Unit order examples:: + + name age arrival squad squad_position profession + +The orderings are defined in ``hack/lua/plugins/sort/*.lua`` + +:dfhack-keybind:`sort-units` diff --git a/docs/plugins/spectate.rst b/docs/plugins/spectate.rst new file mode 100644 index 000000000..6c2ca9e24 --- /dev/null +++ b/docs/plugins/spectate.rst @@ -0,0 +1,5 @@ +spectate +======== +Simple plugin to automate following random dwarves. Most of the time things will +be weighted towards z-levels with the highest job activity. Simply enter the +``spectate`` command to toggle the plugin's state. diff --git a/docs/plugins/spotclean.rst b/docs/plugins/spotclean.rst new file mode 100644 index 000000000..9ccdd2cc6 --- /dev/null +++ b/docs/plugins/spotclean.rst @@ -0,0 +1,6 @@ +spotclean +========= +Works like ``clean map snow mud``, but only for the tile under the cursor. Ideal +if you want to keep that bloody entrance ``clean map`` would clean up. + +:dfhack-keybind:`spotclean` diff --git a/docs/plugins/steam-engine.rst b/docs/plugins/steam-engine.rst new file mode 100644 index 000000000..416ac9ad1 --- /dev/null +++ b/docs/plugins/steam-engine.rst @@ -0,0 +1,84 @@ +steam-engine +============ +The steam-engine plugin detects custom workshops with STEAM_ENGINE in +their token, and turns them into real steam engines. + +The vanilla game contains only water wheels and windmills as sources of +power, but windmills give relatively little power, and water wheels require +flowing water, which must either be a real river and thus immovable and +limited in supply, or actually flowing and thus laggy. + +Compared to the :wiki:`water reactor ` +exploit, steam engines make a lot of sense! + +Construction +------------ +The workshop needs water as its input, which it takes via a +passable floor tile below it, like usual magma workshops do. +The magma version also needs magma. + +Due to DFHack limits, the workshop will collapse over true open space. +However down stairs are passable but support machines, so you can use them. + +After constructing the building itself, machines can be connected +to the edge tiles that look like gear boxes. Their exact position +is extracted from the workshop raws. + +Like with collapse above, due to DFHack limits the workshop +can only immediately connect to machine components built AFTER it. +This also means that engines cannot be chained without intermediate +axles built after both engines. + +Operation +--------- +In order to operate the engine, queue the Stoke Boiler job (optionally +on repeat). A furnace operator will come, possibly bringing a bar of fuel, +and perform it. As a result, a "boiling water" item will appear +in the :kbd:`t` view of the workshop. + +.. note:: + + The completion of the job will actually consume one unit + of the appropriate liquids from below the workshop. This means + that you cannot just raise 7 units of magma with a piston and + have infinite power. However, liquid consumption should be slow + enough that water can be supplied by a pond zone bucket chain. + +Every such item gives 100 power, up to a limit of 300 for coal, +and 500 for a magma engine. The building can host twice that +amount of items to provide longer autonomous running. When the +boiler gets filled to capacity, all queued jobs are suspended; +once it drops back to 3+1 or 5+1 items, they are re-enabled. + +While the engine is providing power, steam is being consumed. +The consumption speed includes a fixed 10% waste rate, and +the remaining 90% are applied proportionally to the actual +load in the machine. With the engine at nominal 300 power with +150 load in the system, it will consume steam for actual +300*(10% + 90%*150/300) = 165 power. + +Masterpiece mechanism and chain will decrease the mechanical +power drawn by the engine itself from 10 to 5. Masterpiece +barrel decreases waste rate by 4%. Masterpiece piston and pipe +decrease it by further 4%, and also decrease the whole steam +use rate by 10%. + +Explosions +---------- +The engine must be constructed using barrel, pipe and piston +from fire-safe, or in the magma version magma-safe metals. + +During operation weak parts get gradually worn out, and +eventually the engine explodes. It should also explode if +toppled during operation by a building destroyer, or a +tantruming dwarf. + +Save files +---------- +It should be safe to load and view engine-using fortresses +from a DF version without DFHack installed, except that in such +case the engines won't work. However actually making modifications +to them, or machines they connect to (including by pulling levers), +can easily result in inconsistent state once this plugin is +available again. The effects may be as weird as negative power +being generated. diff --git a/docs/plugins/stockflow.rst b/docs/plugins/stockflow.rst new file mode 100644 index 000000000..c3db7a673 --- /dev/null +++ b/docs/plugins/stockflow.rst @@ -0,0 +1,31 @@ +stockflow +========= +Allows the fortress bookkeeper to queue jobs through the manager, +based on space or items available in stockpiles. + +Inspired by `workflow`. + +Usage: + +``stockflow enable`` + Enable the plugin. +``stockflow disable`` + Disable the plugin. +``stockflow fast`` + Enable the plugin in fast mode. +``stockflow list`` + List any work order settings for your stockpiles. +``stockflow status`` + Display whether the plugin is enabled. + +While enabled, the :kbd:`q` menu of each stockpile will have two new options: + +* :kbd:`j`: Select a job to order, from an interface like the manager's screen. +* :kbd:`J`: Cycle between several options for how many such jobs to order. + +Whenever the bookkeeper updates stockpile records, new work orders will +be placed on the manager's queue for each such selection, reduced by the +number of identical orders already in the queue. + +In fast mode, new work orders will be enqueued once per day, instead of +waiting for the bookkeeper. diff --git a/docs/plugins/stockpiles.rst b/docs/plugins/stockpiles.rst new file mode 100644 index 000000000..000527ce5 --- /dev/null +++ b/docs/plugins/stockpiles.rst @@ -0,0 +1,28 @@ +.. _stocksettings: + +stockpiles +========== +Offers the following commands to save and load stockpile settings. +See `gui/stockpiles` for an in-game interface. + +:copystock: Copies the parameters of the currently highlighted stockpile to the custom + stockpile settings and switches to custom stockpile placement mode, effectively + allowing you to copy/paste stockpiles easily. + :dfhack-keybind:`copystock` + +:savestock: Saves the currently highlighted stockpile's settings to a file in your Dwarf + Fortress folder. This file can be used to copy settings between game saves or + players. e.g.: ``savestock food_settings.dfstock`` + +:loadstock: Loads a saved stockpile settings file and applies it to the currently selected + stockpile. e.g.: ``loadstock food_settings.dfstock`` + +To use savestock and loadstock, use the :kbd:`q` command to highlight a stockpile. +Then run savestock giving it a descriptive filename. Then, in a different (or +the same!) gameworld, you can highlight any stockpile with :kbd:`q` then execute the +``loadstock`` command passing it the name of that file. The settings will be +applied to that stockpile. + +Note that files are relative to the DF folder, so put your files there or in a +subfolder for easy access. Filenames should not have spaces. Generated materials, +divine metals, etc are not saved as they are different in every world. diff --git a/docs/plugins/stocks.rst b/docs/plugins/stocks.rst new file mode 100644 index 000000000..cbfbce978 --- /dev/null +++ b/docs/plugins/stocks.rst @@ -0,0 +1,6 @@ +stocks +====== +Replaces the DF stocks screen with an improved version. + +:dfhack-keybind:`stocks` + diff --git a/docs/plugins/stonesense.rst b/docs/plugins/stonesense.rst new file mode 100644 index 000000000..b6c154e66 --- /dev/null +++ b/docs/plugins/stonesense.rst @@ -0,0 +1,10 @@ +.. _plugin-stonesense: + +stonesense +========== +An isometric visualizer that runs in a second window. Usage: + +:stonesense: Open the visualiser in a new window. Alias ``ssense``. +:ssense overlay: Overlay DF window, replacing the map area. + +For more information, see `the full Stonesense README `. diff --git a/docs/plugins/strangemood.rst b/docs/plugins/strangemood.rst new file mode 100644 index 000000000..0d578c363 --- /dev/null +++ b/docs/plugins/strangemood.rst @@ -0,0 +1,19 @@ +strangemood +=========== +Creates a strange mood job the same way the game itself normally does it. + +Options: + +:-force: Ignore normal strange mood preconditions (no recent mood, minimum + moodable population, artifact limit not reached). +:-unit: Make the strange mood strike the selected unit instead of picking + one randomly. Unit eligibility is still enforced. +:-type : Force the mood to be of a particular type instead of choosing randomly based on happiness. + Valid values for T are "fey", "secretive", "possessed", "fell", and "macabre". +:-skill S: Force the mood to use a specific skill instead of choosing the highest moodable skill. + Valid values are "miner", "carpenter", "engraver", "mason", "tanner", "weaver", + "clothier", "weaponsmith", "armorsmith", "metalsmith", "gemcutter", "gemsetter", + "woodcrafter", "stonecrafter", "metalcrafter", "glassmaker", "leatherworker", + "bonecarver", "bowyer", and "mechanic". + +Known limitations: if the selected unit is currently performing a job, the mood will not be started. diff --git a/docs/plugins/tailor.rst b/docs/plugins/tailor.rst new file mode 100644 index 000000000..ee1567fdb --- /dev/null +++ b/docs/plugins/tailor.rst @@ -0,0 +1,11 @@ +tailor +====== + +Whenever the bookkeeper updates stockpile records, this plugin will scan every unit in the fort, +count up the number that are worn, and then order enough more made to replace all worn items. +If there are enough replacement items in inventory to replace all worn items, the units wearing them +will have the worn items confiscated (in the same manner as the `cleanowned` plugin) so that they'll +reeequip with replacement items. + +Use the `enable` and `disable ` commands to toggle this plugin's status, or run +``tailor status`` to check its current status. diff --git a/docs/plugins/tiletypes-command.rst b/docs/plugins/tiletypes-command.rst new file mode 100644 index 000000000..0f75c633e --- /dev/null +++ b/docs/plugins/tiletypes-command.rst @@ -0,0 +1,10 @@ +tiletypes-command +----------------- +Runs tiletypes commands, separated by ``;``. This makes it possible to change +tiletypes modes from a hotkey or via dfhack-run. + +Example:: + + tiletypes-command p any ; p s wall ; p sp normal + +This resets the paint filter to unsmoothed walls. diff --git a/docs/plugins/tiletypes-here-point.rst b/docs/plugins/tiletypes-here-point.rst new file mode 100644 index 000000000..8951bdff8 --- /dev/null +++ b/docs/plugins/tiletypes-here-point.rst @@ -0,0 +1,6 @@ +tiletypes-here-point +-------------------- +Apply the current tiletypes options at the in-game cursor position to a single +tile. Can be used from a hotkey. + +This command supports the same options as `tiletypes-here` above. diff --git a/docs/plugins/tiletypes-here.rst b/docs/plugins/tiletypes-here.rst new file mode 100644 index 000000000..fe9ceefd9 --- /dev/null +++ b/docs/plugins/tiletypes-here.rst @@ -0,0 +1,14 @@ +tiletypes-here +-------------- +Apply the current tiletypes options at the in-game cursor position, including +the brush. Can be used from a hotkey. + +Options: + +:``-c``, ``--cursor ,,``: + Use the specified map coordinates instead of the current cursor position. If + this option is specified, then an active game map cursor is not necessary. +:``-h``, ``--help``: + Show command help text. +:``-q``, ``--quiet``: + Suppress non-error status output. diff --git a/docs/plugins/tiletypes.rst b/docs/plugins/tiletypes.rst new file mode 100644 index 000000000..41f780521 --- /dev/null +++ b/docs/plugins/tiletypes.rst @@ -0,0 +1,82 @@ +tiletypes +========= +Can be used for painting map tiles and is an interactive command, much like +`liquids`. Some properties of existing tiles can be looked up with `probe`. If +something goes wrong, `fixveins` may help. + +The tool works with two set of options and a brush. The brush determines which +tiles will be processed. First set of options is the filter, which can exclude +some of the tiles from the brush by looking at the tile properties. The second +set of options is the paint - this determines how the selected tiles are +changed. + +Both paint and filter can have many different properties including things like +general shape (WALL, FLOOR, etc.), general material (SOIL, STONE, MINERAL, +etc.), state of 'designated', 'hidden' and 'light' flags. + +The properties of filter and paint can be partially defined. This means that +you can for example turn all stone fortifications into floors, preserving the +material:: + + filter material STONE + filter shape FORTIFICATION + paint shape FLOOR + +Or turn mineral vein floors back into walls:: + + filter shape FLOOR + filter material MINERAL + paint shape WALL + +The tool also allows tweaking some tile flags:: + + paint hidden 1 + paint hidden 0 + +This will hide previously revealed tiles (or show hidden with the 0 option). + +More recently, the tool supports changing the base material of the tile to +an arbitrary stone from the raws, by creating new veins as required. Note +that this mode paints under ice and constructions, instead of overwriting +them. To enable, use:: + + paint stone MICROCLINE + +This mode is incompatible with the regular ``material`` setting, so changing +it cancels the specific stone selection:: + + paint material ANY + +Since different vein types have different drop rates, it is possible to choose +which one to use in painting:: + + paint veintype CLUSTER_SMALL + +When the chosen type is ``CLUSTER`` (the default), the tool may automatically +choose to use layer stone or lava stone instead of veins if its material matches +the desired one. + +Any paint or filter option (or the entire paint or filter) can be disabled entirely by using the ANY keyword:: + + paint hidden ANY + paint shape ANY + filter material any + filter shape any + filter any + +You can use several different brushes for painting tiles: + +:point: a single tile +:range: a rectangular range +:column: a column ranging from current cursor to the first solid tile above +:block: a DF map block - 16x16 tiles, in a regular grid + +Example:: + + range 10 10 1 + +This will change the brush to a rectangle spanning 10x10 tiles on one z-level. +The range starts at the position of the cursor and goes to the east, south and +up. + +For more details, use ``tiletypes help``. diff --git a/docs/plugins/title-folder.rst b/docs/plugins/title-folder.rst new file mode 100644 index 000000000..4e1ef40ec --- /dev/null +++ b/docs/plugins/title-folder.rst @@ -0,0 +1,3 @@ +title-folder +============= +Displays the DF folder name in the window title bar when enabled. diff --git a/docs/plugins/title-version.rst b/docs/plugins/title-version.rst new file mode 100644 index 000000000..aed7a02e0 --- /dev/null +++ b/docs/plugins/title-version.rst @@ -0,0 +1,3 @@ +title-version +============= +Displays the DFHack version on DF's title screen when enabled. diff --git a/docs/plugins/trackstop.rst b/docs/plugins/trackstop.rst new file mode 100644 index 000000000..b012df34d --- /dev/null +++ b/docs/plugins/trackstop.rst @@ -0,0 +1,5 @@ +trackstop +========= +Adds a :kbd:`q` menu for track stops, which is completely blank by default. +This allows you to view and/or change the track stop's friction and dump +direction settings, using the keybindings from the track stop building interface. diff --git a/docs/plugins/tubefill.rst b/docs/plugins/tubefill.rst new file mode 100644 index 000000000..99c0b76bd --- /dev/null +++ b/docs/plugins/tubefill.rst @@ -0,0 +1,11 @@ +tubefill +======== +Fills all the adamantine veins again. Veins that were hollow will be left +alone. + +Options: + +:hollow: fill in naturally hollow veins too + +Beware that filling in hollow veins will trigger a demon invasion on top of +your miner when you dig into the region that used to be hollow. diff --git a/docs/plugins/tweak.rst b/docs/plugins/tweak.rst new file mode 100644 index 000000000..4ce7b6d38 --- /dev/null +++ b/docs/plugins/tweak.rst @@ -0,0 +1,91 @@ +tweak +===== +Contains various tweaks for minor bugs. + +One-shot subcommands: + +:clear-missing: Remove the missing status from the selected unit. + This allows engraving slabs for ghostly, but not yet + found, creatures. +:clear-ghostly: Remove the ghostly status from the selected unit and mark + it as dead. This allows getting rid of bugged ghosts + which do not show up in the engraving slab menu at all, + even after using clear-missing. It works, but is + potentially very dangerous - so use with care. Probably + (almost certainly) it does not have the same effects like + a proper burial. You've been warned. +:fixmigrant: Remove the resident/merchant flag from the selected unit. + Intended to fix bugged migrants/traders who stay at the + map edge and don't enter your fort. Only works for + dwarves (or generally the player's race in modded games). + Do NOT abuse this for 'real' caravan merchants (if you + really want to kidnap them, use 'tweak makeown' instead, + otherwise they will have their clothes set to forbidden etc). +:makeown: Force selected unit to become a member of your fort. + Can be abused to grab caravan merchants and escorts, even if + they don't belong to the player's race. Foreign sentients + (humans, elves) can be put to work, but you can't assign rooms + to them and they don't show up in DwarfTherapist because the + game treats them like pets. Grabbing draft animals from + a caravan can result in weirdness (animals go insane or berserk + and are not flagged as tame), but you are allowed to mark them + for slaughter. Grabbing wagons results in some funny spam, then + they are scuttled. + +Subcommands that persist until disabled or DF quits: + +.. comment: sort these alphabetically + +:adamantine-cloth-wear: Prevents adamantine clothing from wearing out while being worn (:bug:`6481`). +:advmode-contained: Works around :bug:`6202`, custom reactions with container inputs + in advmode. The issue is that the screen tries to force you to select + the contents separately from the container. This forcefully skips child + reagents. +:block-labors: Prevents labors that can't be used from being toggled +:burrow-name-cancel: Implements the "back" option when renaming a burrow, + which currently does nothing (:bug:`1518`) +:cage-butcher: Adds an option to butcher units when viewing cages with :kbd:`q` +:civ-view-agreement: Fixes overlapping text on the "view agreement" screen +:condition-material: Fixes a crash in the work order contition material list (:bug:`9905`). +:craft-age-wear: Fixes the behavior of crafted items wearing out over time (:bug:`6003`). + With this tweak, items made from cloth and leather will gain a level of + wear every 20 years. +:do-job-now: Adds a job priority toggle to the jobs list +:embark-profile-name: Allows the use of lowercase letters when saving embark profiles +:eggs-fertile: Displays a fertility indicator on nestboxes +:farm-plot-select: Adds "Select all" and "Deselect all" options to farm plot menus +:fast-heat: Further improves temperature update performance by ensuring that 1 degree + of item temperature is crossed in no more than specified number of frames + when updating from the environment temperature. This reduces the time it + takes for stable-temp to stop updates again when equilibrium is disturbed. +:fast-trade: Makes Shift-Down in the Move Goods to Depot and Trade screens select + the current item (fully, in case of a stack), and scroll down one line. +:fps-min: Fixes the in-game minimum FPS setting +:hide-priority: Adds an option to hide designation priority indicators +:hotkey-clear: Adds an option to clear currently-bound hotkeys (in the :kbd:`H` menu) +:import-priority-category: + Allows changing the priority of all goods in a + category when discussing an import agreement with the liaison +:kitchen-prefs-all: Adds an option to toggle cook/brew for all visible items in kitchen preferences +:kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences +:kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs (:bug:`9000`) +:max-wheelbarrow: Allows assigning more than 3 wheelbarrows to a stockpile +:military-color-assigned: + Color squad candidates already assigned to other squads in yellow/green + to make them stand out more in the list. + + .. image:: ../images/tweak-mil-color.png + +:military-stable-assign: + Preserve list order and cursor position when assigning to squad, + i.e. stop the rightmost list of the Positions page of the military + screen from constantly resetting to the top. +:nestbox-color: Fixes the color of built nestboxes +:partial-items: Displays percentages on partially-consumed items such as hospital cloth +:reaction-gloves: Fixes reactions to produce gloves in sets with correct handedness (:bug:`6273`) +:shift-8-scroll: Gives Shift-8 (or :kbd:`*`) priority when scrolling menus, instead of scrolling the map +:stable-cursor: Saves the exact cursor position between t/q/k/d/b/etc menus of fortress mode, if the + map view is near enough to its previous position. +:stone-status-all: Adds an option to toggle the economic status of all stones +:title-start-rename: Adds a safe rename option to the title screen "Start Playing" menu +:tradereq-pet-gender: Displays pet genders on the trade request screen diff --git a/docs/plugins/unreveal.rst b/docs/plugins/unreveal.rst new file mode 100644 index 000000000..da7be8277 --- /dev/null +++ b/docs/plugins/unreveal.rst @@ -0,0 +1,23 @@ +reveal +====== +This reveals the map. By default, HFS will remain hidden so that the demons +don't spawn. You can use ``reveal hell`` to reveal everything. With hell revealed, +you won't be able to unpause until you hide the map again. If you really want +to unpause with hell revealed, use ``reveal demons``. + +Reveal also works in adventure mode, but any of its effects are negated once +you move. When you use it this way, you don't need to run ``unreveal``. + +Usage and related commands: + +:reveal: Reveal the whole map, except for HFS to avoid demons spawning +:reveal hell: Also show hell, but requires ``unreveal`` before unpausing +:reveal demon: Reveals everything and allows unpausing - good luck! +:unreveal: Reverts the effects of ``reveal`` +:revtoggle: Switches between ``reveal`` and ``unreveal`` +:revflood: Hide everything, then reveal tiles with a path to the cursor. + Note that tiles behind constructed walls are also revealed as a + workaround for :bug:`1871`. +:revforget: Discard info about what was visible before revealing the map. + Only useful where (e.g.) you abandoned with the fort revealed + and no longer want the data. diff --git a/docs/plugins/workNow.rst b/docs/plugins/workNow.rst new file mode 100644 index 000000000..bacecf612 --- /dev/null +++ b/docs/plugins/workNow.rst @@ -0,0 +1,12 @@ +workNow +======= +Don't allow dwarves to idle if any jobs are available. + +When workNow is active, every time the game pauses, DF will make dwarves +perform any appropriate available jobs. This includes when you one step +through the game using the pause menu. Usage: + +:workNow: print workNow status +:workNow 0: deactivate workNow +:workNow 1: activate workNow (look for jobs on pause, and only then) +:workNow 2: make dwarves look for jobs whenever a job completes diff --git a/docs/plugins/workflow.rst b/docs/plugins/workflow.rst new file mode 100644 index 000000000..17db57e69 --- /dev/null +++ b/docs/plugins/workflow.rst @@ -0,0 +1,113 @@ +workflow +======== +Manage control of repeat jobs. `gui/workflow` provides a simple +front-end integrated in the game UI. + +Usage: + +``workflow enable [option...], workflow disable [option...]`` + If no options are specified, enables or disables the plugin. + Otherwise, enables or disables any of the following options: + + - drybuckets: Automatically empty abandoned water buckets. + - auto-melt: Resume melt jobs when there are objects to melt. +``workflow jobs`` + List workflow-controlled jobs (if in a workshop, filtered by it). +``workflow list`` + List active constraints, and their job counts. +``workflow list-commands`` + List active constraints as workflow commands that re-create them; + this list can be copied to a file, and then reloaded using the + ``script`` built-in command. +``workflow count [cnt-gap]`` + Set a constraint, counting every stack as 1 item. +``workflow amount [cnt-gap]`` + Set a constraint, counting all items within stacks. +``workflow unlimit `` + Delete a constraint. +``workflow unlimit-all`` + Delete all constraints. + +Function +-------- +When the plugin is enabled, it protects all repeat jobs from removal. +If they do disappear due to any cause, they are immediately re-added to their +workshop and suspended. + +In addition, when any constraints on item amounts are set, repeat jobs that +produce that kind of item are automatically suspended and resumed as the item +amount goes above or below the limit. The gap specifies how much below the limit +the amount has to drop before jobs are resumed; this is intended to reduce +the frequency of jobs being toggled. + +Constraint format +----------------- +The constraint spec consists of 4 parts, separated with ``/`` characters:: + + ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,] + +The first part is mandatory and specifies the item type and subtype, +using the raw tokens for items (the same syntax used custom reaction inputs). +For more information, see :wiki:`this wiki page `. + +The subsequent parts are optional: + +- A generic material spec constrains the item material to one of + the hard-coded generic classes, which currently include:: + + PLANT WOOD CLOTH SILK LEATHER BONE SHELL SOAP TOOTH HORN PEARL YARN + METAL STONE SAND GLASS CLAY MILK + +- A specific material spec chooses the material exactly, using the + raw syntax for reaction input materials, e.g. ``INORGANIC:IRON``, + although for convenience it also allows just ``IRON``, or ``ACACIA:WOOD`` etc. + See the link above for more details on the unabbreviated raw syntax. + +- A comma-separated list of miscellaneous flags, which currently can + be used to ignore imported items or items below a certain quality. + +Constraint examples +------------------- +Keep metal bolts within 900-1000, and wood/bone within 150-200:: + + workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100 + workflow amount AMMO:ITEM_AMMO_BOLTS/WOOD,BONE 200 50 + +Keep the number of prepared food & drink stacks between 90 and 120:: + + workflow count FOOD 120 30 + workflow count DRINK 120 30 + +Make sure there are always 25-30 empty bins/barrels/bags:: + + workflow count BIN 30 + workflow count BARREL 30 + workflow count BOX/CLOTH,SILK,YARN 30 + +Make sure there are always 15-20 coal and 25-30 copper bars:: + + workflow count BAR//COAL 20 + workflow count BAR//COPPER 30 + +Produce 15-20 gold crafts:: + + workflow count CRAFTS//GOLD 20 + +Collect 15-20 sand bags and clay boulders:: + + workflow count POWDER_MISC/SAND 20 + workflow count BOULDER/CLAY 20 + +Make sure there are always 80-100 units of dimple dye:: + + workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20 + +.. note:: + + In order for this to work, you have to set the material of the PLANT input + on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the `job item-material ` + command. Otherwise the plugin won't be able to deduce the output material. + +Maintain 10-100 locally-made crafts of exceptional quality:: + + workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90 diff --git a/docs/plugins/zone.rst b/docs/plugins/zone.rst new file mode 100644 index 000000000..a3112c147 --- /dev/null +++ b/docs/plugins/zone.rst @@ -0,0 +1,130 @@ +zone +==== +Helps a bit with managing activity zones (pens, pastures and pits) and cages. + +:dfhack-keybind:`zone` + +Options: + +:set: Set zone or cage under cursor as default for future assigns. +:assign: Assign unit(s) to the pen or pit marked with the 'set' command. + If no filters are set a unit must be selected in the in-game ui. + Can also be followed by a valid zone id which will be set + instead. +:unassign: Unassign selected creature from it's zone. +:nick: Mass-assign nicknames, must be followed by the name you want + to set. +:remnick: Mass-remove nicknames. +:enumnick: Assign enumerated nicknames (e.g. "Hen 1", "Hen 2"...). Must be + followed by the prefix to use in nicknames. +:tocages: Assign unit(s) to cages inside a pasture. +:uinfo: Print info about unit(s). If no filters are set a unit must + be selected in the in-game ui. +:zinfo: Print info about zone(s). If no filters are set zones under + the cursor are listed. +:verbose: Print some more info. +:filters: Print list of valid filter options. +:examples: Print some usage examples. +:not: Negates the next filter keyword. + +Filters: + +:all: Process all units (to be used with additional filters). +:count: Must be followed by a number. Process only n units (to be used + with additional filters). +:unassigned: Not assigned to zone, chain or built cage. +:minage: Minimum age. Must be followed by number. +:maxage: Maximum age. Must be followed by number. +:race: Must be followed by a race RAW ID (e.g. BIRD_TURKEY, ALPACA, + etc). Negatable. +:caged: In a built cage. Negatable. +:own: From own civilization. Negatable. +:merchant: Is a merchant / belongs to a merchant. Should only be used for + pitting, not for stealing animals (slaughter should work). +:war: Trained war creature. Negatable. +:hunting: Trained hunting creature. Negatable. +:tamed: Creature is tame. Negatable. +:trained: Creature is trained. Finds war/hunting creatures as well as + creatures who have a training level greater than 'domesticated'. + If you want to specifically search for war/hunting creatures use + 'war' or 'hunting' Negatable. +:trainablewar: Creature can be trained for war (and is not already trained for + war/hunt). Negatable. +:trainablehunt: Creature can be trained for hunting (and is not already trained + for war/hunt). Negatable. +:male: Creature is male. Negatable. +:female: Creature is female. Negatable. +:egglayer: Race lays eggs. Negatable. +:grazer: Race is a grazer. Negatable. +:milkable: Race is milkable. Negatable. + +Usage with single units +----------------------- +One convenient way to use the zone tool is to bind the command 'zone assign' to +a hotkey, maybe also the command 'zone set'. Place the in-game cursor over +a pen/pasture or pit, use 'zone set' to mark it. Then you can select units +on the map (in 'v' or 'k' mode), in the unit list or from inside cages +and use 'zone assign' to assign them to their new home. Allows pitting your +own dwarves, by the way. + +Usage with filters +------------------ +All filters can be used together with the 'assign' command. + +Restrictions: It's not possible to assign units who are inside built cages +or chained because in most cases that won't be desirable anyways. +It's not possible to cage owned pets because in that case the owner +uncages them after a while which results in infinite hauling back and forth. + +Usually you should always use the filter 'own' (which implies tame) unless you +want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless +you specify 'race DWARF' (so it's safe to use 'assign all own' to one big +pasture if you want to have all your animals at the same place). 'egglayer' and +'milkable' should be used together with 'female' unless you have a mod with +egg-laying male elves who give milk or whatever. Merchants and their animals are +ignored unless you specify 'merchant' (pitting them should be no problem, +but stealing and pasturing their animals is not a good idea since currently they +are not properly added to your own stocks; slaughtering them should work). + +Most filters can be negated (e.g. 'not grazer' -> race is not a grazer). + +Mass-renaming +------------- +Using the 'nick' command you can set the same nickname for multiple units. +If used without 'assign', 'all' or 'count' it will rename all units in the +current default target zone. Combined with 'assign', 'all' or 'count' (and +further optional filters) it will rename units matching the filter conditions. + +Cage zones +---------- +Using the 'tocages' command you can assign units to a set of cages, for example +a room next to your butcher shop(s). They will be spread evenly among available +cages to optimize hauling to and butchering from them. For this to work you need +to build cages and then place one pen/pasture activity zone above them, covering +all cages you want to use. Then use 'zone set' (like with 'assign') and use +'zone tocages filter1 filter2 ...'. 'tocages' overwrites 'assign' because it +would make no sense, but can be used together with 'nick' or 'remnick' and all +the usual filters. + +Examples +-------- +``zone assign all own ALPACA minage 3 maxage 10`` + Assign all own alpacas who are between 3 and 10 years old to the selected + pasture. +``zone assign all own caged grazer nick ineedgrass`` + Assign all own grazers who are sitting in cages on stockpiles (e.g. after + buying them from merchants) to the selected pasture and give them + the nickname 'ineedgrass'. +``zone assign all own not grazer not race CAT`` + Assign all own animals who are not grazers, excluding cats. +``zone assign count 5 own female milkable`` + Assign up to 5 own female milkable creatures to the selected pasture. +``zone assign all own race DWARF maxage 2`` + Throw all useless kids into a pit :) +``zone nick donttouchme`` + Nicknames all units in the current default zone or cage to 'donttouchme'. + Mostly intended to be used for special pastures or cages which are not marked + as rooms you want to protect from autobutcher. +``zone tocages count 50 own tame male not grazer`` + Stuff up to 50 owned tame male animals who are not grazers into cages built + on the current default zone. From a58b56abc4a0f0197eb6378b201de9044c49b7f5 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 9 Jul 2022 23:16:40 -0700 Subject: [PATCH 186/854] don't error out if files cannot be read --- library/lua/helpdb.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index e72f0804d..83a281884 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -206,8 +206,10 @@ local function make_script_entry(old_entry, entry_name, script_source_path) end local entry = make_default_entry(entry_name, {[ENTRY_TYPES.COMMAND]=true}, HELP_SOURCES.SCRIPT, source_timestamp, script_source_path) + local ok, lines = pcall(io.lines, script_source_path) + if not ok then return entry end local is_rb = script_source_path:endswith('.rb') - update_entry(entry, io.lines(script_source_path), + update_entry(entry, lines, {begin_marker=(is_rb and SCRIPT_DOC_BEGIN_RUBY or SCRIPT_DOC_BEGIN), end_marker=(is_rb and SCRIPT_DOC_BEGIN_RUBY or SCRIPT_DOC_END), first_line_is_short_help=true}) @@ -343,7 +345,9 @@ end -- to tag_index, initizlizing each entry with an empty list. local function initialize_tags() local tag, desc, in_desc = nil, nil, false - for line in io.lines(TAG_DEFINITIONS) do + local ok, lines = pcall(io.lines, TAG_DEFINITIONS) + if not ok then return end + for line in lines do if in_desc then line = line:trim() if #line == 0 then From 12557f8dc14b77ab86f42e448ab8c1c065bb9560 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 10 Jul 2022 06:47:15 +0000 Subject: [PATCH 187/854] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/plugins/diggingInvaders.rst | 1 - docs/plugins/stocks.rst | 1 - 2 files changed, 2 deletions(-) diff --git a/docs/plugins/diggingInvaders.rst b/docs/plugins/diggingInvaders.rst index 0557225fb..09a22c07a 100644 --- a/docs/plugins/diggingInvaders.rst +++ b/docs/plugins/diggingInvaders.rst @@ -26,4 +26,3 @@ Action Cost Delay Notes ``destroyRoughConstruction`` 1,000 1,000 constructions made from boulders ``destroySmoothConstruction`` 100 100 constructions made from blocks or bars ============================== ======= ====== ================================= - diff --git a/docs/plugins/stocks.rst b/docs/plugins/stocks.rst index cbfbce978..404c96a6d 100644 --- a/docs/plugins/stocks.rst +++ b/docs/plugins/stocks.rst @@ -3,4 +3,3 @@ stocks Replaces the DF stocks screen with an improved version. :dfhack-keybind:`stocks` - From fdd406b7220d6c2086a959a2c6cd69a5c7858644 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 9 Jul 2022 23:58:11 -0700 Subject: [PATCH 188/854] ensure all files are reread on every docs build this fixes the issue where the Stonesense docs were getting ignored --- CMakeLists.txt | 4 ++-- conf.py | 2 +- docs/build.sh | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 46bd1917b..a076bbc76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -478,13 +478,13 @@ if(BUILD_DOCS) set_source_files_properties(${SPHINX_OUTPUT} PROPERTIES GENERATED TRUE) add_custom_command(OUTPUT ${SPHINX_OUTPUT} COMMAND ${SPHINX_EXECUTABLE} - -q -b html -d "${CMAKE_BINARY_DIR}/docs/html" + -E -q -b html -d "${CMAKE_BINARY_DIR}/docs/html" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/docs/html" -w "${CMAKE_BINARY_DIR}/docs/html/_sphinx-warnings.txt" -j auto COMMAND ${SPHINX_EXECUTABLE} - -q -b text -d "${CMAKE_BINARY_DIR}/docs/text" + -E -q -b text -d "${CMAKE_BINARY_DIR}/docs/text" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/docs/text" -w "${CMAKE_BINARY_DIR}/docs/text/_sphinx-warnings.txt" diff --git a/conf.py b/conf.py index d7be99a15..530a7a312 100644 --- a/conf.py +++ b/conf.py @@ -140,7 +140,7 @@ def write_tool_docs(): os.makedirs(os.path.join('docs/tools', os.path.dirname(k[0])), mode=0o755, exist_ok=True) with open('docs/tools/{}.rst'.format(k[0]), mode) as outfile: - if k[0] != 'search': + if k[0] != 'search' and k[0] != 'stonesense': outfile.write(label) outfile.write(include) diff --git a/docs/build.sh b/docs/build.sh index c696d5fbe..28182d9c0 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -11,5 +11,5 @@ cd $(dirname "$0") cd .. -"${SPHINX:-sphinx-build}" -b html -d build/docs/html . docs/html -w build/docs/html/_sphinx-warnings.txt -j "${JOBS:-auto}" "$@" -"${SPHINX:-sphinx-build}" -b text -d build/docs/text . docs/text -w build/docs/text/_sphinx-warnings.txt -j "${JOBS:-auto}" "$@" +"${SPHINX:-sphinx-build}" -E -b html -d build/docs/html . docs/html -w build/docs/html/_sphinx-warnings.txt -j "${JOBS:-auto}" "$@" +"${SPHINX:-sphinx-build}" -E -b text -d build/docs/text . docs/text -w build/docs/text/_sphinx-warnings.txt -j "${JOBS:-auto}" "$@" From 4ed15ffcc4032dec4b31def7d02ad8e100c91d5a Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 10 Jul 2022 20:48:24 -0700 Subject: [PATCH 189/854] fix parsing of first line as the short_help --- library/lua/helpdb.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 83a281884..a145ab5f7 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -92,9 +92,10 @@ end -- end_marker (string that marks the end of the help text; text will stop -- being parsed after this marker is seen) -- no_header (don't try to find the entity name at the top of the help text) --- first_line_is_short_help (read the short help text from the first commented --- line of the script instead of using the first --- sentence of the long help text) +-- first_line_is_short_help (if set, then read the short help text from the +-- first commented line of the script instead of +-- using the first sentence of the long help text. +-- value is the comment character.) local function update_entry(entry, iterator, opts) opts = opts or {} local lines = {} @@ -104,7 +105,7 @@ local function update_entry(entry, iterator, opts) for line in iterator do if not short_help_found and first_line_is_short_help then line = line:trim() - local _,_,text = line:find('^%-%-%s*(.*)') or line:find('^#%s*(.*)') + local _,_,text = line:find('^'..first_line_is_short_help..'%s*(.*)') if not text then -- if no first-line short help found, fall back to getting the -- first sentence of the help text. @@ -212,7 +213,7 @@ local function make_script_entry(old_entry, entry_name, script_source_path) update_entry(entry, lines, {begin_marker=(is_rb and SCRIPT_DOC_BEGIN_RUBY or SCRIPT_DOC_BEGIN), end_marker=(is_rb and SCRIPT_DOC_BEGIN_RUBY or SCRIPT_DOC_END), - first_line_is_short_help=true}) + first_line_is_short_help=(is_rb and '#' or '%-%-')}) return entry end From f670ef4b60a694f565745076df6d862cb8d6838e Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Mon, 11 Jul 2022 18:24:07 +0100 Subject: [PATCH 190/854] Add custom raw tokens link and add prefix to custom raw tokens in raws --- docs/guides/modding-guide.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 15e96599d..ea7ae9eb7 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -101,7 +101,7 @@ Check the full list of events at https://docs.dfhack.org/en/stable/docs/Lua%20AP Custom raw tokens ----------------- -In this section, we are going to use custom raw tokens applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how you could make boots that make units go faster when worn. Both of these involve custom raw tokens. +In this section, we are going to use `custom raw tokens <_custom-raw-tokens>` applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how you could make boots that make units go faster when worn. Both of these involve custom raw tokens. First, let's define a custom crossbow with its own custom reaction. The crossbow: :: @@ -117,7 +117,7 @@ First, let's define a custom crossbow with its own custom reaction. The crossbow [MATERIAL_SIZE:4] [ATTACK:BLUNT:10000:4000:bash:bashes:NO_SUB:1250] [ATTACK_PREPARE_AND_RECOVER:3:3] - [FIRE_RATE_MULTIPLIER:2] custom token (you'll see) + [TUTORIAL_MOD_FIRE_RATE_MULTIPLIER:2] custom token (you'll see) The reaction to make it (you would add the reaction and not the weapon to an entity raw): :: @@ -132,7 +132,7 @@ The reaction to make it (you would add the reaction and not the weapon to an ent [ANY_PLANT_MATERIAL] [REAGENT:handle 2:1:BLOCKS:NONE:NONE:NONE] [ANY_PLANT_MATERIAL] - [TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] another custom token + [TUTORIAL_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] another custom token [PRODUCT:100:1:WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE:GET_MATERIAL_FROM_REAGENT:bar:NONE] So, we are going to use the ``eventful`` module to make it so that (after the script is run) when this crossbow is crafted, it will have two handles, each with the material given by the block reagents. @@ -149,7 +149,9 @@ Now, let's make a callback: :: First, we check to see if it the reaction that just happened is relevant to this callback: :: - if not customRawTokens.getToken(reaction, "TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") then return end + if not customRawTokens.getToken(reaction, "TUTORIAL_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") then + return + end Then, we get the product number listed. Next, for every reagent, if the reagent name starts with "handle" then we get the corresponding item, and... :: @@ -186,7 +188,7 @@ Let's also make some code to modify the fire rate of the siege crossbow. :: return end - local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "FIRE_RATE_MULTIPLIER")) or 1 + local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "TUTORIAL_MOD_FIRE_RATE_MULTIPLIER")) or 1 firer.counters.think_counter = math.floor(firer.counters.think_counter * multiplier) end From aa8809833b4048ca2a8d4d0f5489058db67309f7 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Mon, 11 Jul 2022 18:56:28 +0100 Subject: [PATCH 191/854] Do the "running shoes" TODO --- docs/guides/modding-guide.rst | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index ea7ae9eb7..5cafba37d 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -192,7 +192,40 @@ Let's also make some code to modify the fire rate of the siege crossbow. :: firer.counters.think_counter = math.floor(firer.counters.think_counter * multiplier) end -TODO: "running shoes" +Now, let's see how we could make some "pegasus boots". First, let's define the item in the raws: :: + +[ITEM_SHOES:ITEM_SHOES_BOOTS_PEGASUS] + [NAME:pegasus boot:pegasus boots] + [ARMORLEVEL:1] + [UPSTEP:1] + [METAL_ARMOR_LEVELS] + [LAYER:OVER] + [COVERAGE:100] + [LAYER_SIZE:25] + [LAYER_PERMIT:15] + [MATERIAL_SIZE:2] + [METAL] + [LEATHER] + [HARD] + [EXAMPLE_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token (you don't have to say this every time) + +Then, let's make a ``repeat-util`` callback for once a tick: :: + + repeatUtil.scheduleEvery(modId, 1, "ticks", function() + +Let's iterate over every active unit, and for every unit, initialise a variable for how much we are going to take from their movement timer and iterate over all their worn items: :: + + for _, unit in ipairs(df.global.world.units.active) do + local amount = 0 + for _, entry in ipairs(unit.inventory) do + +Now, we will add up the effect of all speed-increasing gear and apply it: :: + + if entry.mode == df.unit_inventory_item.T_mode.Worn then + amount = amount + tonumber((customRawTokens.getToken(entry.item, "EXAMPLE_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK")) or 0) + end + end + dfhack.units.addMoveTimer(-amount) -- Subtract amount from movement timer if currently moving Your first whole mod -------------------- From a5da3c18f9957b26e948e4db3ec87fd2c5dff003 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 11 Jul 2022 17:23:23 -0700 Subject: [PATCH 192/854] reset scroll position when the text is changed --- library/lua/gui/widgets.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 1a1b4c6fb..d5d94ffd9 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -469,7 +469,6 @@ Label.ATTRS{ } function Label:init(args) - self.start_line_num = 1 -- use existing saved text if no explicit text was specified. this avoids -- overwriting pre-formatted text that subclasses may have already set self:setText(args.text or self.text) @@ -479,6 +478,7 @@ function Label:init(args) end function Label:setText(text) + self.start_line_num = 1 self.text = text parse_label_text(self) From 328d839f1938d2cf4623fc6e26db9fada346e354 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 11 Jul 2022 17:23:56 -0700 Subject: [PATCH 193/854] support backtick as a keybinding and bind it to gui/launcher --- data/init/dfhack.keybindings.init | 2 ++ docs/Builtin.rst | 4 ++-- library/Core.cpp | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/data/init/dfhack.keybindings.init b/data/init/dfhack.keybindings.init index e32a7c58e..f38840e06 100644 --- a/data/init/dfhack.keybindings.init +++ b/data/init/dfhack.keybindings.init @@ -8,6 +8,8 @@ # Generic dwarfmode bindings # ############################## +keybinding add ` gui/launcher + # show all current key bindings keybinding add Ctrl-F1 hotkeys keybinding add Alt-F1 hotkeys diff --git a/docs/Builtin.rst b/docs/Builtin.rst index 2d938e30f..cf58337bc 100644 --- a/docs/Builtin.rst +++ b/docs/Builtin.rst @@ -107,8 +107,8 @@ To set keybindings, use the built-in ``keybinding`` command. Like any other command it can be used at any time from the console, but bindings are not remembered between runs of the game unless re-created in `dfhack.init`. -Currently, any combinations of Ctrl/Alt/Shift with A-Z, 0-9, or F1-F12 are -supported. +Currently, any combinations of Ctrl/Alt/Shift with A-Z, 0-9, F1-F12, or ``\``` +are supported. Possible ways to call the command: diff --git a/library/Core.cpp b/library/Core.cpp index 62fe366f1..a4feab742 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2558,6 +2558,9 @@ static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string if (keyspec.size() == 1 && keyspec[0] >= 'A' && keyspec[0] <= 'Z') { *psym = SDL::K_a + (keyspec[0]-'A'); return true; + } else if (keyspec.size() == 1 && keyspec[0] == '`') { + *psym = SDL::K_BACKQUOTE; + return true; } else if (keyspec.size() == 1 && keyspec[0] >= '0' && keyspec[0] <= '9') { *psym = SDL::K_0 + (keyspec[0]-'0'); return true; From d68350c1f116cb569fab996f8743fc099b1d8907 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 11 Jul 2022 17:24:17 -0700 Subject: [PATCH 194/854] wrap text at 52 characters for in-game display --- conf.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/conf.py b/conf.py index 530a7a312..461fa789b 100644 --- a/conf.py +++ b/conf.py @@ -363,3 +363,9 @@ latex_documents = [ ] latex_toplevel_sectioning = 'part' + +# -- Options for text output --------------------------------------------- + +from sphinx.writers import text + +text.MAXWIDTH = 52 From 185f49976c51e0e689c06f6ae67c789a7c8feb92 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 11 Jul 2022 17:24:53 -0700 Subject: [PATCH 195/854] ensure scripts get their entry type set --- library/lua/helpdb.lua | 58 +++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index a145ab5f7..8856c7ad8 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -318,6 +318,7 @@ end -- scan for scripts and add their help to the db local function scan_scripts(old_db, db) + local entry_types = {[ENTRY_TYPES.COMMAND]=true} for _,script_path in ipairs(dfhack.internal.getScriptPaths()) do local files = dfhack.filesystem.listdir_recursive( script_path, nil, false) @@ -330,12 +331,13 @@ local function scan_scripts(old_db, db) goto continue end local dot_index = f.path:find('%.[^.]*$') + local entry_name = f.path:sub(1, dot_index - 1) local script_source = script_path .. '/' .. f.path update_db(old_db, db, - has_rendered_help(f.path) and + has_rendered_help(entry_name) and HELP_SOURCES.RENDERED or HELP_SOURCES.SCRIPT, - f.path:sub(1, dot_index - 1), - {script_source=script_source}) + entry_name, + {entry_types=entry_types, script_source=script_source}) ::continue:: end ::skip_path:: @@ -388,6 +390,33 @@ end -- get API --------------------------------------------------------------------------- +-- converts strings into single-element lists containing that string +local function normalize_string_list(l) + if not l or #l == 0 then return nil end + if type(l) == 'string' then + return {l} + end + return l +end + +local function has_keys(str, dict) + if not str or #str == 0 then + return false + end + ensure_db() + for _,s in ipairs(normalize_string_list(str)) do + if not dict[s] then + return false + end + end + return true +end + +-- returns whether the given string (or list of strings) is an entry in the db +function is_entry(str) + return has_keys(str, db) +end + local function get_db_property(entry_name, property) ensure_db() if not db[entry_name] then @@ -420,19 +449,9 @@ function get_entry_tags(entry) return set_to_sorted_list(get_db_property(entry, 'tags')) end --- returns whether the given string matches a tag name +-- returns whether the given string (or list of strings) matches a tag name function is_tag(str) - if not str or #str == 0 then - return false - end - ensure_db() - if type(str) == "string" then str = {str} end - for _,s in ipairs(str) do - if not tag_index[s] then - return false - end - end - return true + return has_keys(str, tag_index) end -- returns the defined tags in alphabetical order @@ -524,15 +543,6 @@ local function matches(entry_name, filter) return true end --- converts strings into single-element lists containing that string -local function normalize_string_list(l) - if not l or #l == 0 then return nil end - if type(l) == 'string' then - return {l} - end - return l -end - -- normalizes the lists in the filter and returns nil if no filter elements are -- populated local function normalize_filter(f) From c9cffc7da92e25bf9a1032e18a81eb000aac8f73 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 12 Jul 2022 11:26:49 +0100 Subject: [PATCH 196/854] Fix docs bugs --- docs/guides/modding-guide.rst | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 5cafba37d..990c0ce96 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -101,7 +101,7 @@ Check the full list of events at https://docs.dfhack.org/en/stable/docs/Lua%20AP Custom raw tokens ----------------- -In this section, we are going to use `custom raw tokens <_custom-raw-tokens>` applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how you could make boots that make units go faster when worn. Both of these involve custom raw tokens. +In this section, we are going to use `custom raw tokens ` applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how you could make boots that make units go faster when worn. Both of these involve custom raw tokens. First, let's define a custom crossbow with its own custom reaction. The crossbow: :: @@ -194,20 +194,20 @@ Let's also make some code to modify the fire rate of the siege crossbow. :: Now, let's see how we could make some "pegasus boots". First, let's define the item in the raws: :: -[ITEM_SHOES:ITEM_SHOES_BOOTS_PEGASUS] - [NAME:pegasus boot:pegasus boots] - [ARMORLEVEL:1] - [UPSTEP:1] - [METAL_ARMOR_LEVELS] - [LAYER:OVER] - [COVERAGE:100] - [LAYER_SIZE:25] - [LAYER_PERMIT:15] - [MATERIAL_SIZE:2] - [METAL] - [LEATHER] - [HARD] - [EXAMPLE_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token (you don't have to say this every time) + [ITEM_SHOES:ITEM_SHOES_BOOTS_PEGASUS] + [NAME:pegasus boot:pegasus boots] + [ARMORLEVEL:1] + [UPSTEP:1] + [METAL_ARMOR_LEVELS] + [LAYER:OVER] + [COVERAGE:100] + [LAYER_SIZE:25] + [LAYER_PERMIT:15] + [MATERIAL_SIZE:2] + [METAL] + [LEATHER] + [HARD] + [EXAMPLE_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token (you don't have to say this every time) Then, let's make a ``repeat-util`` callback for once a tick: :: From f021dd0e0a468204f08808a8b0d63a9b66647ad7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 12 Jul 2022 11:25:16 -0400 Subject: [PATCH 197/854] Gui::getAnyItem(): add support for viewscreen_treasurelistst --- docs/changelog.txt | 1 + library/modules/Gui.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 9bc714e48..447f2f22e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -56,6 +56,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Removed ``Notes`` module (C++-only). Access ``ui.waypoints.points`` directly instead. - Removed ``Windows`` module (C++-only) - unused. - ``Constructions`` module (C++-only): removed ``t_construction``, ``isValid()``, ``getCount()``, ``getConstruction()``, and ``copyConstruction()``. Access ``world.constructions`` directly instead. +- ``Gui::getSelectedItem()``, ``Gui::getAnyItem()``: added support for the artifacts screen ## Lua - ``tile-material``: fix the order of declarations. The ``GetTileMat`` function now returns the material as intended (always returned nil before). Also changed the license info, with permission of the original author. diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 963b2ecd6..fc78bb57d 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -106,6 +106,7 @@ using namespace DFHack; #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_unitst.h" #include "df/viewscreen_reportlistst.h" +#include "df/viewscreen_treasurelistst.h" #include "df/viewscreen_workquota_conditionst.h" #include "df/viewscreen_workshop_profilest.h" #include "df/world.h" @@ -1181,6 +1182,13 @@ df::item *Gui::getAnyItem(df::viewscreen *top) return NULL; } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_treasurelistst, top)) + { + if (world) + return vector_get(world->items.other[df::items_other_id::ANY_ARTIFACT], screen->sel_idx); + return NULL; + } + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) return dfscreen->getSelectedItem(); From c39e2fe2cbf88a2e88552f7b09f2bdc28d199dca Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Wed, 13 Jul 2022 22:42:35 +0100 Subject: [PATCH 198/854] Mod structure --- docs/guides/modding-guide.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 990c0ce96..abc5e5e01 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -232,4 +232,15 @@ Your first whole mod Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. -TODO +Create a folder for mod projects within your Dwarf Fortress directory somewhere (e.g. ``hack/my-scripts/mods/``) and use your mod ID (in hyphen-case) as the name for the mod folders within it. The structure of and environment for fully-functioning modular mods are as follows: + +* The main content of the mod would be in the ``raw`` folder: + + * A Lua file in ``raw/init.d/`` to initialise the mod by calling ``your-mod-id/main/ enable``. + * Raw content (potentially with custom raw tokens) in ``raw/objects/``. + * A subfolder for your mod in ``raw/scripts/`` containing a ``main.lua`` file (an example of which we will see) and all the modules containing the functions used in callbacks to ``repeat-util`` and ``eventful``. Potentially a file containing constant definitions used by your mod (perhaps defined by the game) too. + +* Using git within each mod folder is recommended, but not required. +* A ``readme.mkd`` markdown file is also recommended. +* An ``addToEntity.txt`` file containing lines to add to entity definitions for access to mod content would be needed if applicable. +* Unless you want to merge your ``raw`` folder with your worlds every time you make a change to your scripts, you should add ``path/to/your-mod/raw/scripts/`` to your script paths. From 9c04a28bd96503e4a2ff1b2e52745b7326f5a4ff Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Wed, 13 Jul 2022 22:43:01 +0100 Subject: [PATCH 199/854] Shouldn't've replaced the TODO --- docs/guides/modding-guide.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index abc5e5e01..d52607798 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -244,3 +244,5 @@ Create a folder for mod projects within your Dwarf Fortress directory somewhere * A ``readme.mkd`` markdown file is also recommended. * An ``addToEntity.txt`` file containing lines to add to entity definitions for access to mod content would be needed if applicable. * Unless you want to merge your ``raw`` folder with your worlds every time you make a change to your scripts, you should add ``path/to/your-mod/raw/scripts/`` to your script paths. + +TODO From d7976e63b60ab3664485f7b16b9c8101a8d8e654 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Wed, 13 Jul 2022 22:44:38 +0100 Subject: [PATCH 200/854] const example. also, (preemptively acquiescing) readme.mkd --> readme.md --- docs/guides/modding-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index d52607798..033ddc731 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -238,10 +238,10 @@ Create a folder for mod projects within your Dwarf Fortress directory somewhere * A Lua file in ``raw/init.d/`` to initialise the mod by calling ``your-mod-id/main/ enable``. * Raw content (potentially with custom raw tokens) in ``raw/objects/``. - * A subfolder for your mod in ``raw/scripts/`` containing a ``main.lua`` file (an example of which we will see) and all the modules containing the functions used in callbacks to ``repeat-util`` and ``eventful``. Potentially a file containing constant definitions used by your mod (perhaps defined by the game) too. + * A subfolder for your mod in ``raw/scripts/`` containing a ``main.lua`` file (an example of which we will see) and all the modules containing the functions used in callbacks to ``repeat-util`` and ``eventful``. Potentially a file containing constant definitions used by your mod (perhaps defined by the game, like the acceleration of parabolic projectiles due to gravity (``4900``)) too. * Using git within each mod folder is recommended, but not required. -* A ``readme.mkd`` markdown file is also recommended. +* A ``readme.md`` markdown file is also recommended. * An ``addToEntity.txt`` file containing lines to add to entity definitions for access to mod content would be needed if applicable. * Unless you want to merge your ``raw`` folder with your worlds every time you make a change to your scripts, you should add ``path/to/your-mod/raw/scripts/`` to your script paths. From a996b29cf5845024ac50dacb7c35c097c28abcda Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Thu, 14 Jul 2022 11:10:23 +0100 Subject: [PATCH 201/854] Some editing of mod guide --- docs/guides/modding-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 033ddc731..6b18188a0 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -230,9 +230,9 @@ Now, we will add up the effect of all speed-increasing gear and apply it: :: Your first whole mod -------------------- -Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. +Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. Alternatively you can create multiple callbacks with your mod ID being a prefix, though this way there is no guarantee about the order if that is important. -Create a folder for mod projects within your Dwarf Fortress directory somewhere (e.g. ``hack/my-scripts/mods/``) and use your mod ID (in hyphen-case) as the name for the mod folders within it. The structure of and environment for fully-functioning modular mods are as follows: +Create a folder for mod projects somewhere (e.g. ``hack/my-scripts/mods/``, or maybe somewhere outside your Dwarf Fortress installation) and use your mod ID (in hyphen-case) as the name for the mod folders within it. The structure of and environment for fully-functioning modular mods are as follows: * The main content of the mod would be in the ``raw`` folder: From d06a63e4dc583732f70bce03945f6da20d1e4e6d Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Thu, 14 Jul 2022 11:11:50 +0100 Subject: [PATCH 202/854] DFStructs (a nickname?) --> df-structures --- docs/guides/modding-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 6b18188a0..1badad2dd 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -55,8 +55,8 @@ Now, the field ``sex`` in a unit is an integer, but each integer corresponds to Simple. Save this as a Lua file in your own scripts directory and run it as shown before when focused on a unit one way or another. -Getting used to gm-editor and DFStructs exploration ---------------------------------------------------- +Getting used to gm-editor and df-structures exploration +------------------------------------------------------- So how could you have known about the field and type we just used? Well, there are two main tools for discovering the various fields in the game's data structures. The first is the ``df-structures`` repository (at https://github.com/DFHack/df-structures) that contains XML files denoting the contents of the game's structures. The second is the script ``gui/gm-editor`` which is an interactive data explorer. You can run the script while objects like units are selected to view the data within them. You can also pass ``scr`` as an argument to the script to view the data for the current screen. Press ? while the script is active to view help. From 3f848b8836ef8eeb6cc720c298539b2e62696998 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Thu, 14 Jul 2022 17:46:12 +0100 Subject: [PATCH 203/854] Misc minor changes to modding guide --- docs/guides/modding-guide.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 1badad2dd..d0c9cb398 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -22,7 +22,7 @@ Scripts can be run from a world's ``raw/scripts/`` directory, and (configurably) A script is run by writing its path and name from a script path folder without the file extension into a DFHack command prompt (in-game or the external one). E.g. ``gui/gm-editor`` for ``hack/scripts/gui/gm-editor.lua``. -You can make all your scripts in ``hack/scripts/``, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to ``dfhack-config/script-paths.txt``. You should also make a folder for external installed scripts from the internet that are not in ``hack/scripts/``. You can prepend your script paths entries with a ``+`` so that they take precedence over other folders. +You can make all your scripts in ``hack/scripts/``, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like ``own-scripts`` and add it to ``dfhack-config/script-paths.txt``. You could also make a folder for external installed scripts from the internet that are not in ``hack/scripts/``. You can prepend your script paths entries with a ``+`` so that they take precedence over other folders. If your mod is installed into ``raw/scripts/`` be aware that the copies of the scripts in ``data/save/*/raw/`` are checked first and will run instead of any changes you make to an in-development copy outside of a raw folder. @@ -75,13 +75,13 @@ First, we load the module: :: Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. It's recommended to use something like a mod id. :: - local modId = "my-test-mod" + local modId = "callback-example-mod" Then, we pass the key, amount of time units between function calls, what the time units are, and finally the callback function itself: :: repeatUtil.scheduleEvery(modId, 1, "ticks", function() - -- Do something like iterating over all units - for _, unit in ipairs(df.global.world.units.all) do + -- Do something like iterating over all active units + for _, unit in ipairs(df.global.world.units.active) do print(unit.id) end end) @@ -117,7 +117,7 @@ First, let's define a custom crossbow with its own custom reaction. The crossbow [MATERIAL_SIZE:4] [ATTACK:BLUNT:10000:4000:bash:bashes:NO_SUB:1250] [ATTACK_PREPARE_AND_RECOVER:3:3] - [TUTORIAL_MOD_FIRE_RATE_MULTIPLIER:2] custom token (you'll see) + [SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER:2] custom token (you'll see) The reaction to make it (you would add the reaction and not the weapon to an entity raw): :: @@ -132,7 +132,7 @@ The reaction to make it (you would add the reaction and not the weapon to an ent [ANY_PLANT_MATERIAL] [REAGENT:handle 2:1:BLOCKS:NONE:NONE:NONE] [ANY_PLANT_MATERIAL] - [TUTORIAL_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] another custom token + [SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] another custom token [PRODUCT:100:1:WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE:GET_MATERIAL_FROM_REAGENT:bar:NONE] So, we are going to use the ``eventful`` module to make it so that (after the script is run) when this crossbow is crafted, it will have two handles, each with the material given by the block reagents. @@ -144,12 +144,12 @@ First, require the modules we are going to use. :: Now, let's make a callback: :: - local modId = "siege-crossbow" + local modId = "siege-crossbow-mod" eventful.onReactionComplete[modId] = function(reaction, reactionProduct, unit, inputItems, inputReagents, outputItems) First, we check to see if it the reaction that just happened is relevant to this callback: :: - if not customRawTokens.getToken(reaction, "TUTORIAL_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") then + if not customRawTokens.getToken(reaction, "SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") then return end @@ -188,7 +188,7 @@ Let's also make some code to modify the fire rate of the siege crossbow. :: return end - local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "TUTORIAL_MOD_FIRE_RATE_MULTIPLIER")) or 1 + local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER")) or 1 firer.counters.think_counter = math.floor(firer.counters.think_counter * multiplier) end @@ -207,7 +207,7 @@ Now, let's see how we could make some "pegasus boots". First, let's define the i [METAL] [LEATHER] [HARD] - [EXAMPLE_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token (you don't have to say this every time) + [PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token (you don't have to comment this every time) Then, let's make a ``repeat-util`` callback for once a tick: :: @@ -222,7 +222,7 @@ Let's iterate over every active unit, and for every unit, initialise a variable Now, we will add up the effect of all speed-increasing gear and apply it: :: if entry.mode == df.unit_inventory_item.T_mode.Worn then - amount = amount + tonumber((customRawTokens.getToken(entry.item, "EXAMPLE_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK")) or 0) + amount = amount + tonumber((customRawTokens.getToken(entry.item, "PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK")) or 0) end end dfhack.units.addMoveTimer(-amount) -- Subtract amount from movement timer if currently moving From e926e1116eb9490ccb6ce875a86dd33418f303c8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 14 Jul 2022 13:19:30 -0700 Subject: [PATCH 204/854] replace more Core cpp code with calls to helpdb also document devel/dump-rpc builtin --- docs/Builtin.rst | 11 + library/Core.cpp | 1104 ++++++++++++++++-------------------- library/LuaApi.cpp | 10 + library/include/LuaTools.h | 2 + library/lua/helpdb.lua | 28 +- 5 files changed, 547 insertions(+), 608 deletions(-) diff --git a/docs/Builtin.rst b/docs/Builtin.rst index cf58337bc..8e59840b4 100644 --- a/docs/Builtin.rst +++ b/docs/Builtin.rst @@ -255,6 +255,17 @@ type ---- ``type command`` shows where ``command`` is implemented. +.. _devel/dump-rpc: + +devel/dump-rpc +-------------- + +Writes RPC endpoint information to the specified file. + +Usage:: + + devel/dump-rpc FILENAME + Other Commands -------------- The following commands are *not* built-in, but offer similarly useful functions. diff --git a/library/Core.cpp b/library/Core.cpp index a4feab742..30b87007d 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -277,65 +277,6 @@ static string dfhack_version_desc() return s.str(); } -static std::string getScriptHelp(std::string path, std::string helpprefix) -{ - ifstream script(path.c_str()); - - if (script.good()) - { - std::string help; - if (getline(script, help) && - help.substr(0,helpprefix.length()) == helpprefix) - { - help = help.substr(helpprefix.length()); - while (help.size() && help[0] == ' ') - help = help.substr(1); - return help; - } - } - - return "No help available."; -} - -static void listScripts(PluginManager *plug_mgr, std::map &pset, std::string path, bool all, std::string prefix = "") -{ - std::vector files; - Filesystem::listdir(path, files); - - path += '/'; - for (size_t i = 0; i < files.size(); i++) - { - if (hasEnding(files[i], ".lua")) - { - string help = getScriptHelp(path + files[i], "--"); - string key = prefix + files[i].substr(0, files[i].size()-4); - if (pset.find(key) == pset.end()) { - pset[key] = help; - } - } - else if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() && hasEnding(files[i], ".rb")) - { - string help = getScriptHelp(path + files[i], "#"); - string key = prefix + files[i].substr(0, files[i].size()-3); - if (pset.find(key) == pset.end()) { - pset[key] = help; - } - } - else if (all && !files[i].empty() && files[i][0] != '.' && files[i] != "internal" && files[i] != "test") - { - listScripts(plug_mgr, pset, path+files[i]+"/", all, prefix+files[i]+"/"); - } - } -} - -static void listAllScripts(map &pset, bool all) -{ - vector paths; - Core::getInstance().getScriptPaths(&paths); - for (string path : paths) - listScripts(Core::getInstance().getPluginManager(), pset, path, all); -} - namespace { struct ScriptArgs { const string *pcmd; @@ -442,60 +383,52 @@ command_result Core::runCommand(color_ostream &out, const std::string &command) return CR_NOT_IMPLEMENTED; } -// List of built in commands -static const std::set built_in_commands = { - "ls" , - "help" , - "tags" , - "type" , - "load" , - "unload" , - "reload" , - "enable" , - "disable" , - "plug" , - "keybinding" , - "alias" , - "fpause" , - "cls" , - "die" , - "kill-lua" , - "script" , - "hide" , - "show" , - "sc-script" -}; +bool is_builtin(color_ostream &con, const string &command) { + CoreSuspender suspend; + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); -static bool try_autocomplete(color_ostream &con, const std::string &first, std::string &completed) -{ - std::vector possible; + if (!lua_checkstack(L, 1) || + !Lua::PushModulePublic(con, L, "helpdb", "is_builtin")) { + con.printerr("Failed to load helpdb Lua code\n"); + return false; + } - // Check for possible built in commands to autocomplete first - for (auto const &command : built_in_commands) - if (command.substr(0, first.size()) == first) - possible.push_back(command); + Lua::Push(L, command); - auto plug_mgr = Core::getInstance().getPluginManager(); - for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) - { - const Plugin * plug = it->second; - for (size_t j = 0; j < plug->size(); j++) - { - const PluginCommand &pcmd = (*plug)[j]; - if (pcmd.isHotkeyCommand()) - continue; - if (pcmd.name.substr(0, first.size()) == first) - possible.push_back(pcmd.name); - } + if (!Lua::SafeCall(con, L, 1, 1)) { + con.printerr("Failed Lua call to helpdb.is_builtin.\n"); + return false; + } + + return lua_toboolean(L, -1); +} + +void get_commands(color_ostream &con, vector &commands) { + CoreSuspender suspend; + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 1) || + !Lua::PushModulePublic(con, L, "helpdb", "get_commands")) { + con.printerr("Failed to load helpdb Lua code\n"); + return; } - bool all = (first.find('/') != std::string::npos); + if (!Lua::SafeCall(con, L, 0, 1)) { + con.printerr("Failed Lua call to helpdb.get_commands.\n"); + } - std::map scripts; - listAllScripts(scripts, all); - for (auto iter = scripts.begin(); iter != scripts.end(); ++iter) - if (iter->first.substr(0, first.size()) == first) - possible.push_back(iter->first); + Lua::GetVector(L, commands); +} + +static bool try_autocomplete(color_ostream &con, const std::string &first, std::string &completed) +{ + std::vector commands, possible; + + for (auto &command : commands) + if (command.substr(0, first.size()) == first) + possible.push_back(command); if (possible.size() == 1) { @@ -651,30 +584,6 @@ static std::string sc_event_name (state_change_event id) { return "SC_UNKNOWN"; } -string getBuiltinCommand(std::string cmd) -{ - std::string builtin = ""; - - // Check our list of builtin commands from the header - if (built_in_commands.count(cmd)) - builtin = cmd; - - // Check for some common aliases for built in commands - else if (cmd == "?" || cmd == "man") - builtin = "help"; - - else if (cmd == "dir") - builtin = "ls"; - - else if (cmd == "clear") - builtin = "cls"; - - else if (cmd == "devel/dump-rpc") - builtin = "devel/dump-rpc"; - - return builtin; -} - void help_helper(color_ostream &con, const string &entry_name) { CoreSuspender suspend; auto L = Lua::Core::State; @@ -753,587 +662,582 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v return CR_FAILURE; } - command_result res; - if (!first.empty()) + if (first.empty()) + return CR_NOT_IMPLEMENTED; + + if (first.find('\\') != std::string::npos) { - if(first.find('\\') != std::string::npos) + con.printerr("Replacing backslashes with forward slashes in \"%s\"\n", first.c_str()); + for (size_t i = 0; i < first.size(); i++) { - con.printerr("Replacing backslashes with forward slashes in \"%s\"\n", first.c_str()); - for (size_t i = 0; i < first.size(); i++) - { - if (first[i] == '\\') - first[i] = '/'; - } + if (first[i] == '\\') + first[i] = '/'; } + } - // let's see what we actually got - string builtin = getBuiltinCommand(first); - if (builtin == "help") + // let's see what we actually got + command_result res; + if (first == "help" || first == "man" || first == "?") + { + if(!parts.size()) { - if(!parts.size()) - { - if (con.is_console()) - { - con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n" - "Some basic editing capabilities are included (single-line text editing).\n" - "The console also has a command history - you can navigate it with Up and Down keys.\n" - "On Windows, you may have to resize your console window. The appropriate menu is accessible\n" - "by clicking on the program icon in the top bar of the window.\n\n"); - } - con.print("Here are some basic commands to get you started:\n" - " help|?|man - This text.\n" - " help - Usage help for the given plugin, command, or script.\n" - " tags - List the tags that the DFHack tools are grouped by.\n" - " ls|dir [] - List commands, optionally filtered by a tag or substring.\n" - " Optional parameters:\n" - " --notags: skip printing tags for each command.\n" - " --dev: include commands intended for developers and modders.\n" - " cls|clear - Clear the console.\n" - " fpause - Force DF to pause.\n" - " die - Force DF to close immediately, without saving.\n" - " keybinding - Modify bindings of commands to in-game key shortcuts.\n" - "\n" - "See more commands by running 'ls'.\n\n" - ); - - con.print("DFHack version %s\n", dfhack_version_desc().c_str()); - } - else + if (con.is_console()) { - help_helper(con, parts[0]); + con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n" + "Some basic editing capabilities are included (single-line text editing).\n" + "The console also has a command history - you can navigate it with Up and Down keys.\n" + "On Windows, you may have to resize your console window. The appropriate menu is accessible\n" + "by clicking on the program icon in the top bar of the window.\n\n"); } + con.print("Here are some basic commands to get you started:\n" + " help|?|man - This text.\n" + " help - Usage help for the given plugin, command, or script.\n" + " tags - List the tags that the DFHack tools are grouped by.\n" + " ls|dir [] - List commands, optionally filtered by a tag or substring.\n" + " Optional parameters:\n" + " --notags: skip printing tags for each command.\n" + " --dev: include commands intended for developers and modders.\n" + " cls|clear - Clear the console.\n" + " fpause - Force DF to pause.\n" + " die - Force DF to close immediately, without saving.\n" + " keybinding - Modify bindings of commands to in-game key shortcuts.\n" + "\n" + "See more commands by running 'ls'.\n\n" + ); + + con.print("DFHack version %s\n", dfhack_version_desc().c_str()); } - else if (builtin == "tags") + else { - tags_helper(con); + help_helper(con, parts[0]); } - else if (builtin == "load" || builtin == "unload" || builtin == "reload") + } + else if (first == "tags") + { + tags_helper(con); + } + else if (first == "load" || first == "unload" || first == "reload") + { + bool all = false; + bool load = (first == "load"); + bool unload = (first == "unload"); + if (parts.size()) { - bool all = false; - bool load = (builtin == "load"); - bool unload = (builtin == "unload"); - if (parts.size()) + for (auto p = parts.begin(); p != parts.end(); p++) { - for (auto p = parts.begin(); p != parts.end(); p++) - { - if (p->size() && (*p)[0] == '-') - { - if (p->find('a') != string::npos) - all = true; - } - } - if (all) - { - if (load) - plug_mgr->loadAll(); - else if (unload) - plug_mgr->unloadAll(); - else - plug_mgr->reloadAll(); - return CR_OK; - } - for (auto p = parts.begin(); p != parts.end(); p++) + if (p->size() && (*p)[0] == '-') { - if (!p->size() || (*p)[0] == '-') - continue; - if (load) - plug_mgr->load(*p); - else if (unload) - plug_mgr->unload(*p); - else - plug_mgr->reload(*p); + if (p->find('a') != string::npos) + all = true; } } - else - con.printerr("%s: no arguments\n", builtin.c_str()); + if (all) + { + if (load) + plug_mgr->loadAll(); + else if (unload) + plug_mgr->unloadAll(); + else + plug_mgr->reloadAll(); + return CR_OK; + } + for (auto p = parts.begin(); p != parts.end(); p++) + { + if (!p->size() || (*p)[0] == '-') + continue; + if (load) + plug_mgr->load(*p); + else if (unload) + plug_mgr->unload(*p); + else + plug_mgr->reload(*p); + } } - else if( builtin == "enable" || builtin == "disable" ) - { - CoreSuspender suspend; - bool enable = (builtin == "enable"); + else + con.printerr("%s: no arguments\n", first.c_str()); + } + else if( first == "enable" || first == "disable" ) + { + CoreSuspender suspend; + bool enable = (first == "enable"); - if(parts.size()) + if(parts.size()) + { + for (size_t i = 0; i < parts.size(); i++) { - for (size_t i = 0; i < parts.size(); i++) + std::string part = parts[i]; + if (part.find('\\') != std::string::npos) { - std::string part = parts[i]; - if (part.find('\\') != std::string::npos) + con.printerr("Replacing backslashes with forward slashes in \"%s\"\n", part.c_str()); + for (size_t j = 0; j < part.size(); j++) { - con.printerr("Replacing backslashes with forward slashes in \"%s\"\n", part.c_str()); - for (size_t j = 0; j < part.size(); j++) - { - if (part[j] == '\\') - part[j] = '/'; - } + if (part[j] == '\\') + part[j] = '/'; } + } - Plugin * plug = (*plug_mgr)[part]; + Plugin * plug = (*plug_mgr)[part]; - if(!plug) - { - std::string lua = findScript(part + ".lua"); - if (lua.size()) - { - res = enableLuaScript(con, part, enable); - } - else - { - res = CR_NOT_FOUND; - con.printerr("No such plugin or Lua script: %s\n", part.c_str()); - } - } - else if (!plug->can_set_enabled()) + if(!plug) + { + std::string lua = findScript(part + ".lua"); + if (lua.size()) { - res = CR_NOT_IMPLEMENTED; - con.printerr("Cannot %s plugin: %s\n", builtin.c_str(), part.c_str()); + res = enableLuaScript(con, part, enable); } else { - res = plug->set_enabled(con, enable); - - if (res != CR_OK || plug->is_enabled() != enable) - con.printerr("Could not %s plugin: %s\n", builtin.c_str(), part.c_str()); + res = CR_NOT_FOUND; + con.printerr("No such plugin or Lua script: %s\n", part.c_str()); } } - - return res; - } - else - { - for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) + else if (!plug->can_set_enabled()) + { + res = CR_NOT_IMPLEMENTED; + con.printerr("Cannot %s plugin: %s\n", first.c_str(), part.c_str()); + } + else { - Plugin * plug = it->second; - if (!plug->can_be_enabled()) continue; - - con.print( - "%20s\t%-3s%s\n", - (plug->getName()+":").c_str(), - plug->is_enabled() ? "on" : "off", - plug->can_set_enabled() ? "" : " (controlled elsewhere)" - ); + res = plug->set_enabled(con, enable); + + if (res != CR_OK || plug->is_enabled() != enable) + con.printerr("Could not %s plugin: %s\n", first.c_str(), part.c_str()); } } + + return res; } - else if (builtin == "ls" || builtin == "dir") - { - ls_helper(con, parts); - } - else if (builtin == "plug") + else { - const char *header_format = "%30s %10s %4s %8s\n"; - const char *row_format = "%30s %10s %4i %8s\n"; - con.print(header_format, "Name", "State", "Cmds", "Enabled"); - - plug_mgr->refresh(); for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { Plugin * plug = it->second; - if (!plug) - continue; - if (parts.size() && std::find(parts.begin(), parts.end(), plug->getName()) == parts.end()) - continue; - color_value color; - switch (plug->getState()) - { - case Plugin::PS_LOADED: - color = COLOR_RESET; - break; - case Plugin::PS_UNLOADED: - case Plugin::PS_UNLOADING: - color = COLOR_YELLOW; - break; - case Plugin::PS_LOADING: - color = COLOR_LIGHTBLUE; - break; - case Plugin::PS_BROKEN: - color = COLOR_LIGHTRED; - break; - default: - color = COLOR_LIGHTMAGENTA; - break; - } - con.color(color); - con.print(row_format, - plug->getName().c_str(), - Plugin::getStateDescription(plug->getState()), - plug->size(), - (plug->can_be_enabled() - ? (plug->is_enabled() ? "enabled" : "disabled") - : "n/a") + if (!plug->can_be_enabled()) continue; + + con.print( + "%20s\t%-3s%s\n", + (plug->getName()+":").c_str(), + plug->is_enabled() ? "on" : "off", + plug->can_set_enabled() ? "" : " (controlled elsewhere)" ); - con.color(COLOR_RESET); } } - else if (builtin == "type") + } + else if (first == "ls" || first == "dir") + { + ls_helper(con, parts); + } + else if (first == "plug") + { + const char *header_format = "%30s %10s %4s %8s\n"; + const char *row_format = "%30s %10s %4i %8s\n"; + con.print(header_format, "Name", "State", "Cmds", "Enabled"); + + plug_mgr->refresh(); + for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { - if (!parts.size()) - { - con.printerr("type: no argument\n"); - return CR_WRONG_USAGE; - } - con << parts[0]; - string builtin_cmd = getBuiltinCommand(parts[0]); - string lua_path = findScript(parts[0] + ".lua"); - string ruby_path = findScript(parts[0] + ".rb"); - Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); - if (builtin_cmd.size()) - { - con << " is a built-in command"; - if (builtin_cmd != parts[0]) - con << " (aliased to " << builtin_cmd << ")"; - con << std::endl; - } - else if (IsAlias(parts[0])) - { - con << " is an alias: " << GetAliasCommand(parts[0]) << std::endl; - } - else if (plug) - { - con << " is a command implemented by the plugin " << plug->getName() << std::endl; - } - else if (lua_path.size()) - { - con << " is a Lua script: " << lua_path << std::endl; - } - else if (ruby_path.size()) - { - con << " is a Ruby script: " << ruby_path << std::endl; - } - else + Plugin * plug = it->second; + if (!plug) + continue; + if (parts.size() && std::find(parts.begin(), parts.end(), plug->getName()) == parts.end()) + continue; + color_value color; + switch (plug->getState()) { - con << " is not a recognized command." << std::endl; - plug = plug_mgr->getPluginByName(parts[0]); - if (plug) - con << "Plugin " << parts[0] << " exists and implements " << plug->size() << " commands." << std::endl; - return CR_FAILURE; + case Plugin::PS_LOADED: + color = COLOR_RESET; + break; + case Plugin::PS_UNLOADED: + case Plugin::PS_UNLOADING: + color = COLOR_YELLOW; + break; + case Plugin::PS_LOADING: + color = COLOR_LIGHTBLUE; + break; + case Plugin::PS_BROKEN: + color = COLOR_LIGHTRED; + break; + default: + color = COLOR_LIGHTMAGENTA; + break; } + con.color(color); + con.print(row_format, + plug->getName().c_str(), + Plugin::getStateDescription(plug->getState()), + plug->size(), + (plug->can_be_enabled() + ? (plug->is_enabled() ? "enabled" : "disabled") + : "n/a") + ); + con.color(COLOR_RESET); } - else if (builtin == "keybinding") + } + else if (first == "type") + { + if (!parts.size()) { - if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) - { - std::string keystr = parts[1]; - if (parts[0] == "set") - ClearKeyBindings(keystr); - for (int i = parts.size()-1; i >= 2; i--) - { - if (!AddKeyBinding(keystr, parts[i])) { - con.printerr("Invalid key spec: %s\n", keystr.c_str()); - break; - } - } - } - else if (parts.size() >= 2 && parts[0] == "clear") - { - for (size_t i = 1; i < parts.size(); i++) - { - if (!ClearKeyBindings(parts[i])) { - con.printerr("Invalid key spec: %s\n", parts[i].c_str()); - break; - } - } - } - else if (parts.size() == 2 && parts[0] == "list") - { - std::vector list = ListKeyBindings(parts[1]); - if (list.empty()) - con << "No bindings." << endl; - for (size_t i = 0; i < list.size(); i++) - con << " " << list[i] << endl; - } - else - { - con << "Usage:" << endl - << " keybinding list " << endl - << " keybinding clear [@context]..." << endl - << " keybinding set [@context] \"cmdline\" \"cmdline\"..." << endl - << " keybinding add [@context] \"cmdline\" \"cmdline\"..." << endl - << "Later adds, and earlier items within one command have priority." << endl - << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, or Enter)." << endl - << "Context may be used to limit the scope of the binding, by" << endl - << "requiring the current context to have a certain prefix." << endl - << "Current UI context is: " - << Gui::getFocusString(Core::getTopViewscreen()) << endl; - } + con.printerr("type: no argument\n"); + return CR_WRONG_USAGE; } - else if (builtin == "alias") + con << parts[0]; + bool builtin = is_builtin(con, parts[0]); + string lua_path = findScript(parts[0] + ".lua"); + string ruby_path = findScript(parts[0] + ".rb"); + Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); + if (builtin) { - if (parts.size() >= 3 && (parts[0] == "add" || parts[0] == "replace")) - { - const string &name = parts[1]; - vector cmd(parts.begin() + 2, parts.end()); - if (!AddAlias(name, cmd, parts[0] == "replace")) - { - con.printerr("Could not add alias %s - already exists\n", name.c_str()); - return CR_FAILURE; - } - } - else if (parts.size() >= 2 && (parts[0] == "delete" || parts[0] == "clear")) + con << " is a built-in command"; + con << std::endl; + } + else if (IsAlias(parts[0])) + { + con << " is an alias: " << GetAliasCommand(parts[0]) << std::endl; + } + else if (plug) + { + con << " is a command implemented by the plugin " << plug->getName() << std::endl; + } + else if (lua_path.size()) + { + con << " is a Lua script: " << lua_path << std::endl; + } + else if (ruby_path.size()) + { + con << " is a Ruby script: " << ruby_path << std::endl; + } + else + { + con << " is not a recognized command." << std::endl; + plug = plug_mgr->getPluginByName(parts[0]); + if (plug) + con << "Plugin " << parts[0] << " exists and implements " << plug->size() << " commands." << std::endl; + return CR_FAILURE; + } + } + else if (first == "keybinding") + { + if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) + { + std::string keystr = parts[1]; + if (parts[0] == "set") + ClearKeyBindings(keystr); + for (int i = parts.size()-1; i >= 2; i--) { - if (!RemoveAlias(parts[1])) - { - con.printerr("Could not remove alias %s\n", parts[1].c_str()); - return CR_FAILURE; + if (!AddKeyBinding(keystr, parts[i])) { + con.printerr("Invalid key spec: %s\n", keystr.c_str()); + break; } } - else if (parts.size() >= 1 && (parts[0] == "list")) + } + else if (parts.size() >= 2 && parts[0] == "clear") + { + for (size_t i = 1; i < parts.size(); i++) { - auto aliases = ListAliases(); - for (auto p : aliases) - { - con << p.first << ": " << join_strings(" ", p.second) << endl; + if (!ClearKeyBindings(parts[i])) { + con.printerr("Invalid key spec: %s\n", parts[i].c_str()); + break; } } - else + } + else if (parts.size() == 2 && parts[0] == "list") + { + std::vector list = ListKeyBindings(parts[1]); + if (list.empty()) + con << "No bindings." << endl; + for (size_t i = 0; i < list.size(); i++) + con << " " << list[i] << endl; + } + else + { + con << "Usage:" << endl + << " keybinding list " << endl + << " keybinding clear [@context]..." << endl + << " keybinding set [@context] \"cmdline\" \"cmdline\"..." << endl + << " keybinding add [@context] \"cmdline\" \"cmdline\"..." << endl + << "Later adds, and earlier items within one command have priority." << endl + << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, or Enter)." << endl + << "Context may be used to limit the scope of the binding, by" << endl + << "requiring the current context to have a certain prefix." << endl + << "Current UI context is: " + << Gui::getFocusString(Core::getTopViewscreen()) << endl; + } + } + else if (first == "alias") + { + if (parts.size() >= 3 && (parts[0] == "add" || parts[0] == "replace")) + { + const string &name = parts[1]; + vector cmd(parts.begin() + 2, parts.end()); + if (!AddAlias(name, cmd, parts[0] == "replace")) { - con << "Usage: " << endl - << " alias add|replace " << endl - << " alias delete|clear " << endl - << " alias list" << endl; + con.printerr("Could not add alias %s - already exists\n", name.c_str()); + return CR_FAILURE; } } - else if (builtin == "fpause") + else if (parts.size() >= 2 && (parts[0] == "delete" || parts[0] == "clear")) { - World::SetPauseState(true); - if (auto scr = Gui::getViewscreenByType()) + if (!RemoveAlias(parts[1])) { - scr->worldgen_paused = true; + con.printerr("Could not remove alias %s\n", parts[1].c_str()); + return CR_FAILURE; } - con.print("The game was forced to pause!\n"); } - else if (builtin == "cls") + else if (parts.size() >= 1 && (parts[0] == "list")) { - if (con.is_console()) - ((Console&)con).clear(); - else + auto aliases = ListAliases(); + for (auto p : aliases) { - con.printerr("No console to clear.\n"); - return CR_NEEDS_CONSOLE; + con << p.first << ": " << join_strings(" ", p.second) << endl; } } - else if (builtin == "die") + else { - std::_Exit(666); + con << "Usage: " << endl + << " alias add|replace " << endl + << " alias delete|clear " << endl + << " alias list" << endl; } - else if (builtin == "kill-lua") + } + else if (first == "fpause") + { + World::SetPauseState(true); + if (auto scr = Gui::getViewscreenByType()) { - bool force = false; - for (auto it = parts.begin(); it != parts.end(); ++it) - { - if (*it == "force") - force = true; - } - if (!Lua::Interrupt(force)) - { - con.printerr( - "Failed to register hook. This can happen if you have" - " lua profiling or coverage monitoring enabled. Use" - " 'kill-lua force' to force, but this may disable" - " profiling and coverage monitoring.\n"); - } + scr->worldgen_paused = true; } - else if (builtin == "script") + con.print("The game was forced to pause!\n"); + } + else if (first == "cls" || first == "clear") + { + if (con.is_console()) + ((Console&)con).clear(); + else { - if(parts.size() == 1) - { - loadScriptFile(con, parts[0], false); - } - else - { - con << "Usage:" << endl - << " script " << endl; - return CR_WRONG_USAGE; - } + con.printerr("No console to clear.\n"); + return CR_NEEDS_CONSOLE; } - else if (builtin=="hide") + } + else if (first == "die") + { + std::_Exit(666); + } + else if (first == "kill-lua") + { + bool force = false; + for (auto it = parts.begin(); it != parts.end(); ++it) + { + if (*it == "force") + force = true; + } + if (!Lua::Interrupt(force)) + { + con.printerr( + "Failed to register hook. This can happen if you have" + " lua profiling or coverage monitoring enabled. Use" + " 'kill-lua force' to force, but this may disable" + " profiling and coverage monitoring.\n"); + } + } + else if (first == "script") + { + if(parts.size() == 1) + { + loadScriptFile(con, parts[0], false); + } + else + { + con << "Usage:" << endl + << " script " << endl; + return CR_WRONG_USAGE; + } + } + else if (first == "hide") + { + if (!getConsole().hide()) + { + con.printerr("Could not hide console\n"); + return CR_FAILURE; + } + return CR_OK; + } + else if (first == "show") + { + if (!getConsole().show()) { - if (!getConsole().hide()) + con.printerr("Could not show console\n"); + return CR_FAILURE; + } + return CR_OK; + } + else if (first == "sc-script") + { + if (parts.empty() || parts[0] == "help" || parts[0] == "?") + { + con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << endl; + con << "Valid event names (SC_ prefix is optional):" << endl; + for (int i = SC_WORLD_LOADED; i <= SC_UNPAUSED; i++) { - con.printerr("Could not hide console\n"); - return CR_FAILURE; + std::string name = sc_event_name((state_change_event)i); + if (name != "SC_UNKNOWN") + con << " " << name << endl; } return CR_OK; } - else if (builtin=="show") + else if (parts[0] == "list") { - if (!getConsole().show()) + if(parts.size() < 2) + parts.push_back(""); + if (parts[1].size() && sc_event_id(parts[1]) == SC_UNKNOWN) { - con.printerr("Could not show console\n"); - return CR_FAILURE; + con << "Unrecognized event name: " << parts[1] << endl; + return CR_WRONG_USAGE; + } + for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) + { + if (!parts[1].size() || (it->event == sc_event_id(parts[1]))) + { + con.print("%s (%s): %s%s\n", sc_event_name(it->event).c_str(), + it->save_specific ? "save-specific" : "global", + it->save_specific ? "/raw/" : "/", + it->path.c_str()); + } } return CR_OK; } - else if (builtin == "sc-script") + else if (parts[0] == "add") { - if (parts.empty() || parts[0] == "help" || parts[0] == "?") + if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) { - con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << endl; - con << "Valid event names (SC_ prefix is optional):" << endl; - for (int i = SC_WORLD_LOADED; i <= SC_UNPAUSED; i++) - { - std::string name = sc_event_name((state_change_event)i); - if (name != "SC_UNKNOWN") - con << " " << name << endl; - } - return CR_OK; + con << "Usage: sc-script add EVENT path-to-script [-save]" << endl; + return CR_WRONG_USAGE; } - else if (parts[0] == "list") + state_change_event evt = sc_event_id(parts[1]); + if (evt == SC_UNKNOWN) { - if(parts.size() < 2) - parts.push_back(""); - if (parts[1].size() && sc_event_id(parts[1]) == SC_UNKNOWN) - { - con << "Unrecognized event name: " << parts[1] << endl; - return CR_WRONG_USAGE; - } - for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) - { - if (!parts[1].size() || (it->event == sc_event_id(parts[1]))) - { - con.print("%s (%s): %s%s\n", sc_event_name(it->event).c_str(), - it->save_specific ? "save-specific" : "global", - it->save_specific ? "/raw/" : "/", - it->path.c_str()); - } - } - return CR_OK; + con << "Unrecognized event: " << parts[1] << endl; + return CR_FAILURE; } - else if (parts[0] == "add") + bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); + StateChangeScript script(evt, parts[2], save_specific); + for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) { - if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) - { - con << "Usage: sc-script add EVENT path-to-script [-save]" << endl; - return CR_WRONG_USAGE; - } - state_change_event evt = sc_event_id(parts[1]); - if (evt == SC_UNKNOWN) + if (script == *it) { - con << "Unrecognized event: " << parts[1] << endl; + con << "Script already registered" << endl; return CR_FAILURE; } - bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); - StateChangeScript script(evt, parts[2], save_specific); - for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) - { - if (script == *it) - { - con << "Script already registered" << endl; - return CR_FAILURE; - } - } - state_change_scripts.push_back(script); - return CR_OK; } - else if (parts[0] == "remove") + state_change_scripts.push_back(script); + return CR_OK; + } + else if (parts[0] == "remove") + { + if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) { - if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) - { - con << "Usage: sc-script remove EVENT path-to-script [-save]" << endl; - return CR_WRONG_USAGE; - } - state_change_event evt = sc_event_id(parts[1]); - if (evt == SC_UNKNOWN) - { - con << "Unrecognized event: " << parts[1] << endl; - return CR_FAILURE; - } - bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); - StateChangeScript tmp(evt, parts[2], save_specific); - auto it = std::find(state_change_scripts.begin(), state_change_scripts.end(), tmp); - if (it != state_change_scripts.end()) - { - state_change_scripts.erase(it); - return CR_OK; - } - else - { - con << "Unrecognized script" << endl; - return CR_FAILURE; - } + con << "Usage: sc-script remove EVENT path-to-script [-save]" << endl; + return CR_WRONG_USAGE; + } + state_change_event evt = sc_event_id(parts[1]); + if (evt == SC_UNKNOWN) + { + con << "Unrecognized event: " << parts[1] << endl; + return CR_FAILURE; + } + bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); + StateChangeScript tmp(evt, parts[2], save_specific); + auto it = std::find(state_change_scripts.begin(), state_change_scripts.end(), tmp); + if (it != state_change_scripts.end()) + { + state_change_scripts.erase(it); + return CR_OK; } else { - con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << endl; - return CR_WRONG_USAGE; + con << "Unrecognized script" << endl; + return CR_FAILURE; } } - else if (builtin == "devel/dump-rpc") + else { - if (parts.size() == 1) - { - std::ofstream file(parts[0]); - CoreService core; - core.dumpMethods(file); + con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << endl; + return CR_WRONG_USAGE; + } + } + else if (first == "devel/dump-rpc") + { + if (parts.size() == 1) + { + std::ofstream file(parts[0]); + CoreService core; + core.dumpMethods(file); - for (auto & it : *plug_mgr) - { - Plugin * plug = it.second; - if (!plug) - continue; + for (auto & it : *plug_mgr) + { + Plugin * plug = it.second; + if (!plug) + continue; - std::unique_ptr svc(plug->rpc_connect(con)); - if (!svc) - continue; + std::unique_ptr svc(plug->rpc_connect(con)); + if (!svc) + continue; - file << "// Plugin: " << plug->getName() << endl; - svc->dumpMethods(file); - } - } - else - { - con << "Usage: devel/dump-rpc \"filename\"" << endl; - return CR_WRONG_USAGE; + file << "// Plugin: " << plug->getName() << endl; + svc->dumpMethods(file); } } - else if (RunAlias(con, first, parts, res)) + else { - return res; + con << "Usage: devel/dump-rpc \"filename\"" << endl; + return CR_WRONG_USAGE; } - else + } + else if (RunAlias(con, first, parts, res)) + { + return res; + } + else + { + res = plug_mgr->InvokeCommand(con, first, parts); + if(res == CR_NOT_IMPLEMENTED) { - res = plug_mgr->InvokeCommand(con, first, parts); - if(res == CR_NOT_IMPLEMENTED) + string completed; + string filename = findScript(first + ".lua"); + bool lua = filename != ""; + if ( !lua ) { + filename = findScript(first + ".rb"); + } + if ( lua ) + res = runLuaScript(con, first, parts); + else if ( filename != "" && plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) + res = runRubyScript(con, plug_mgr, filename, parts); + else if ( try_autocomplete(con, first, completed) ) + res = CR_NOT_IMPLEMENTED; + else + con.printerr("%s is not a recognized command.\n", first.c_str()); + if (res == CR_NOT_IMPLEMENTED) { - string completed; - string filename = findScript(first + ".lua"); - bool lua = filename != ""; - if ( !lua ) { - filename = findScript(first + ".rb"); - } - if ( lua ) - res = runLuaScript(con, first, parts); - else if ( filename != "" && plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) - res = runRubyScript(con, plug_mgr, filename, parts); - else if ( try_autocomplete(con, first, completed) ) - res = CR_NOT_IMPLEMENTED; - else - con.printerr("%s is not a recognized command.\n", first.c_str()); - if (res == CR_NOT_IMPLEMENTED) + Plugin *p = plug_mgr->getPluginByName(first); + if (p) { - Plugin *p = plug_mgr->getPluginByName(first); - if (p) - { - con.printerr("%s is a plugin ", first.c_str()); - if (p->getState() == Plugin::PS_UNLOADED) - con.printerr("that is not loaded - try \"load %s\" or check stderr.log\n", - first.c_str()); - else if (p->size()) - con.printerr("that implements %zi commands - see \"ls %s\" for details\n", - p->size(), first.c_str()); - else - con.printerr("but does not implement any commands\n"); - } + con.printerr("%s is a plugin ", first.c_str()); + if (p->getState() == Plugin::PS_UNLOADED) + con.printerr("that is not loaded - try \"load %s\" or check stderr.log\n", + first.c_str()); + else if (p->size()) + con.printerr("that implements %zi commands - see \"ls %s\" for details\n", + p->size(), first.c_str()); + else + con.printerr("but does not implement any commands\n"); } } - else if (res == CR_NEEDS_CONSOLE) - con.printerr("%s needs interactive console to work.\n", first.c_str()); - return res; } - - return CR_OK; + else if (res == CR_NEEDS_CONSOLE) + con.printerr("%s needs interactive console to work.\n", first.c_str()); + return res; } - return CR_NOT_IMPLEMENTED; + return CR_OK; } bool Core::loadScriptFile(color_ostream &out, string fname, bool silent) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index fb557329d..af8eee3c1 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -148,6 +148,16 @@ void Lua::Push(lua_State *state, df::coord2d pos) lua_setfield(state, -2, "y"); } +void GetVector(lua_State *state, std::vector &pvec) +{ + lua_pushnil(state); // first key + while (lua_next(state, 1) != 0) + { + pvec.push_back(lua_tostring(state, -1)); + lua_pop(state, 1); // remove value, leave key + } +} + int Lua::PushPosXYZ(lua_State *state, df::coord pos) { if (!pos.isValid()) diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index df89d184f..6dc5ae0bd 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -339,6 +339,8 @@ namespace DFHack {namespace Lua { } } + DFHACK_EXPORT void GetVector(lua_State *state, std::vector &pvec); + DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos); DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos); diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 8856c7ad8..b52f91f22 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -250,6 +250,7 @@ local BUILTINS = { alias='Configure helper aliases for other DFHack commands.', cls='Clear the console screen.', clear='Clear the console screen.', + ['devel/dump-rpc']='Write RPC endpoint information to a file.', die='Force DF to close immediately, without saving.', enable='Enable a plugin or persistent script.', disable='Disable a plugin or persistent script.', @@ -575,15 +576,26 @@ function search_entries(include, exclude) ensure_db() include = normalize_filter(include) exclude = normalize_filter(exclude) - local commands = {} - for command in pairs(db) do - if (not include or matches(command, include)) and - (not exclude or not matches(command, exclude)) then - table.insert(commands, command) + local entries = {} + for entry in pairs(db) do + if (not include or matches(entry, include)) and + (not exclude or not matches(entry, exclude)) then + table.insert(entries, entry) end end - table.sort(commands, sort_by_basename) - return commands + table.sort(entries, sort_by_basename) + return entries +end + +-- returns a list of all commands. used by Core's autocomplete functionality. +function get_commands() + local include = {types={ENTRY_TYPES.COMMAND}} + return search_entries(include) +end + +function is_builtin(command) + ensure_db() + return db[command] and db[command].entry_types[ENTRY_TYPES.BUILTIN] end --------------------------------------------------------------------------- @@ -633,7 +645,7 @@ function list_entries(skip_tags, include, exclude) end end if #entries == 0 then - print('no entries found.') + print('No matches.') end end From 193b9a40040dc52cde722cea4415371681b2ea73 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 14 Jul 2022 14:08:33 -0700 Subject: [PATCH 205/854] add missing namespace which did not cause compiler errors for some reason --- library/LuaApi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index af8eee3c1..056a89a07 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -148,7 +148,7 @@ void Lua::Push(lua_State *state, df::coord2d pos) lua_setfield(state, -2, "y"); } -void GetVector(lua_State *state, std::vector &pvec) +void Lua::GetVector(lua_State *state, std::vector &pvec) { lua_pushnil(state); // first key while (lua_next(state, 1) != 0) From dd6fbd53b6498ddcc544fdff3eeffb3f6cdde95a Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 15 Jul 2022 09:07:14 -0700 Subject: [PATCH 206/854] add getEntries() to the CommandHistory API so we can export them to lua also bump the default history size to 5000 from the paltry 100 we had --- library/include/Console.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/library/include/Console.h b/library/include/Console.h index 0882ba449..39a19b152 100644 --- a/library/include/Console.h +++ b/library/include/Console.h @@ -32,6 +32,7 @@ distribution. #include #include #include +#include namespace tthread { class mutex; @@ -44,7 +45,7 @@ namespace DFHack class CommandHistory { public: - CommandHistory(std::size_t capacity = 100) + CommandHistory(std::size_t capacity = 5000) { this->capacity = capacity; } @@ -114,6 +115,12 @@ namespace DFHack { history.pop_front(); } + /// adds the current list of entries to the given vector + void getEntries(std::vector &entries) + { + for (auto &entry : history) + entries.push_back(entry); + } private: std::size_t capacity; std::deque history; From c9a87511bd46d8e69cf7e816195e70edbf50e66a Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 15 Jul 2022 09:18:27 -0700 Subject: [PATCH 207/854] add dfhack history repository and expose to lua --- docs/Lua API.rst | 16 +++++++++++++++- library/LuaApi.cpp | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 55a217757..fb2b18291 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -524,6 +524,20 @@ Input & Output lock. Using an explicit ``dfhack.with_suspend`` will prevent this, forcing the function to block on input with lock held. +* ``dfhack.getCommandHistory(history_id, history_filename)`` + + Returns the list of strings in the specified history. Intended to be used by + GUI scripts that don't have access to a console and so can't use + ``dfhack.lineedit``. The ``history_id`` parameter is some unique string that + the script uses to identify its command history, such as the script's name. If + this is the first time the history with the given ``history_id`` is being + accessed, it is initialized from the given file. + +* ``dfhack.addCommandToHistory(history_id, history_filename, command)`` + + Adds a command to the specified history and saves the updated history to the + specified file. + * ``dfhack.interpreter([prompt[,history_filename[,env]]])`` Starts an interactive lua interpreter, using the specified prompt @@ -837,6 +851,7 @@ can be omitted. * ``dfhack.getGitXmlExpectedCommit()`` * ``dfhack.gitXmlMatch()`` * ``dfhack.isRelease()`` +* ``dfhack.isPrerelease()`` Return information about the DFHack build in use. @@ -890,7 +905,6 @@ can be omitted. from ``dfhack.TranslateName()``), use ``print(dfhack.df2console(text))`` to ensure proper display on all platforms. - * ``dfhack.utf2df(string)`` Convert a string from UTF-8 to DF's CP437 encoding. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 056a89a07..649ceb2dc 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1369,6 +1369,38 @@ static void OpenRandom(lua_State *state) lua_pop(state, 1); } + +/********************************* +* Commandline history repository * +**********************************/ + +static std::map commandHistories; + +static CommandHistory * ensureCommandHistory(std::string id, + std::string src_file) { + if (!commandHistories.count(id)) { + commandHistories[id].load(src_file.c_str()); + } + return &commandHistories[id]; +} + +static int getCommandHistory(lua_State *state) +{ + std::string id = lua_tostring(state, 1); + std::string src_file = lua_tostring(state, 2); + std::vector entries; + ensureCommandHistory(id, src_file)->getEntries(entries); + Lua::PushVector(state, entries); + return 1; +} + +static void addCommandToHistory(std::string id, std::string src_file, + std::string command) { + CommandHistory *history = ensureCommandHistory(id, src_file); + history->add(command); + history->save(src_file.c_str()); +} + /************************ * Wrappers for C++ API * ************************/ @@ -1460,6 +1492,12 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP_VERSION_FUNC(gitXmlMatch, git_xml_match), WRAP_VERSION_FUNC(isRelease, is_release), WRAP_VERSION_FUNC(isPrerelease, is_prerelease), + WRAP(addCommandToHistory), + { NULL, NULL } +}; + +static const luaL_Reg dfhack_funcs[] = { + { "getCommandHistory", getCommandHistory }, { NULL, NULL } }; @@ -3199,6 +3237,7 @@ void OpenDFHackApi(lua_State *state) OpenRandom(state); LuaWrapper::SetFunctionWrappers(state, dfhack_module); + luaL_setfuncs(state, dfhack_funcs, 0); OpenModule(state, "gui", dfhack_gui_module, dfhack_gui_funcs); OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs); From f3539f06c8bf22a57acd5bedb7b06b2ff749b436 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 15 Jul 2022 09:44:24 -0700 Subject: [PATCH 208/854] integrate hotkeys with helpdb --- plugins/CMakeLists.txt | 2 +- plugins/hotkeys.cpp | 57 +++++++++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 732673926..af646759f 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -130,7 +130,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(forceequip forceequip.cpp) dfhack_plugin(generated-creature-renamer generated-creature-renamer.cpp) dfhack_plugin(getplants getplants.cpp) - dfhack_plugin(hotkeys hotkeys.cpp) + dfhack_plugin(hotkeys hotkeys.cpp LINK_LIBRARIES lua) dfhack_plugin(infiniteSky infiniteSky.cpp) dfhack_plugin(isoworldremote isoworldremote.cpp PROTOBUFS isoworldremote) dfhack_plugin(jobutils jobutils.cpp) diff --git a/plugins/hotkeys.cpp b/plugins/hotkeys.cpp index e4cd13574..a91c62bdf 100644 --- a/plugins/hotkeys.cpp +++ b/plugins/hotkeys.cpp @@ -8,6 +8,7 @@ #include "modules/World.h" #include "modules/Gui.h" +#include "LuaTools.h" #include "PluginManager.h" DFHACK_PLUGIN("hotkeys"); @@ -54,6 +55,8 @@ static void find_active_keybindings(df::viewscreen *screen) valid_keys.push_back("F" + int_to_string(i)); } + valid_keys.push_back("`"); + auto current_focus = Gui::getFocusString(screen); for (int shifted = 0; shifted < 2; shifted++) { @@ -120,6 +123,29 @@ static void invoke_command(const size_t index) } } +static std::string get_help(const std::string &command, bool full_help) +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 2) || + !Lua::PushModulePublic(out, L, "helpdb", + full_help ? "get_entry_long_help" : "get_entry_short_help")) + return "Help text unavailable."; + + Lua::Push(L, command); + + if (!Lua::SafeCall(out, L, 1, 1)) + return "Help text unavailable."; + + const char *s = lua_tostring(L, -1); + if (!s) + return "Help text unavailable."; + + return s; +} + class ViewscreenHotkeys : public dfhack_viewscreen { public: @@ -219,31 +245,16 @@ public: if (first[0] == '#') return; - Plugin *plugin = Core::getInstance().getPluginManager()->getPluginByCommand(first); - if (plugin) + OutputString(COLOR_BROWN, x, y, "Help", true, help_start); + string help_text = get_help(first, show_usage); + vector lines; + split_string(&lines, help_text, "\n"); + for (auto it = lines.begin(); it != lines.end() && y < gps->dimy - 4; it++) { - for (size_t i = 0; i < plugin->size(); i++) + auto wrapped_lines = wrapString(*it, width); + for (auto wit = wrapped_lines.begin(); wit != wrapped_lines.end() && y < gps->dimy - 4; wit++) { - auto pc = plugin->operator[](i); - if (pc.name == first) - { - OutputString(COLOR_BROWN, x, y, "Help", true, help_start); - vector lines; - string help_text = pc.description; - if (show_usage) - help_text += "\n\n" + pc.usage; - - split_string(&lines, help_text, "\n"); - for (auto it = lines.begin(); it != lines.end() && y < gps->dimy - 4; it++) - { - auto wrapped_lines = wrapString(*it, width); - for (auto wit = wrapped_lines.begin(); wit != wrapped_lines.end() && y < gps->dimy - 4; wit++) - { - OutputString(COLOR_WHITE, x, y, *wit, true, help_start); - } - } - break; - } + OutputString(COLOR_WHITE, x, y, *wit, true, help_start); } } } From 0e704f39f7cdb4710923df648a000e7db635c0ed Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Fri, 15 Jul 2022 19:28:59 +0100 Subject: [PATCH 209/854] Got the really big part of the guide done --- docs/Lua API.rst | 2 + docs/guides/modding-guide.rst | 97 +++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index f2f252c4b..c459bf1f2 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -5040,6 +5040,8 @@ General script API Importing scripts ================= +.. _reqscript: + * ``dfhack.reqscript(name)`` or ``reqscript(name)`` Loads a Lua script and returns its environment (i.e. a table of all global diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index d0c9cb398..aa07ca8a1 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -227,10 +227,10 @@ Now, we will add up the effect of all speed-increasing gear and apply it: :: end dfhack.units.addMoveTimer(-amount) -- Subtract amount from movement timer if currently moving -Your first whole mod --------------------- +The structure of a full mod +--------------------------- -Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. Alternatively you can create multiple callbacks with your mod ID being a prefix, though this way there is no guarantee about the order if that is important. +Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. Alternatively you can create multiple callbacks with your mod ID being a prefix, though this way there is no guarantee about the order if that is important. You will have to use your mod ID as a prefix if you register multiple ``repeat-util`` callbacks, though. Create a folder for mod projects somewhere (e.g. ``hack/my-scripts/mods/``, or maybe somewhere outside your Dwarf Fortress installation) and use your mod ID (in hyphen-case) as the name for the mod folders within it. The structure of and environment for fully-functioning modular mods are as follows: @@ -245,4 +245,93 @@ Create a folder for mod projects somewhere (e.g. ``hack/my-scripts/mods/``, or m * An ``addToEntity.txt`` file containing lines to add to entity definitions for access to mod content would be needed if applicable. * Unless you want to merge your ``raw`` folder with your worlds every time you make a change to your scripts, you should add ``path/to/your-mod/raw/scripts/`` to your script paths. -TODO +Now, let's take a look at an example ``main.lua`` file. :: + + local repeatUtil = require("repeat-util") + local eventful = require("plugins.eventful") + + local modId = "example-mod" + local args = {...} + + if args[1] == "enable" then + -- The modules and what they link into the environment with + -- Each module exports functions named the way they are to be used + local moduleA = dfhack.reqscript("example-mod/module-a") -- on load, every tick + local moduleB = dfhack.reqscript("example-mod/module-b") -- on load, on unload, onReactionComplete + local moduleC = dfhack.reqscript("example-mod/module-c") -- onReactionComplete + local moduleD = dfhack.reqscript("example-mod/module-d") -- every 100 frames, onProjItemCheckMovement, onProjUnitCheckMovement + + -- Set up the modules + -- Order: on load, repeat-util ticks (from smallest interval to largest), days, months, years, and frames, then eventful callbacks in the same order as the first modules to use them + + moduleA.onLoad() + moduleB.onLoad() + + repeatUtil.scheduleEvery(modId .. " 1 ticks", 1, "ticks", function() + moduleA.every1Tick() + end) + + repeatUtil.scheduleEvery(modID .. " 100 frames", 1, "frames", function() + moduleD.every100Frames() + end + + eventful.onReactionComplete[modId] = function(...) + -- Pass the event's parameters to the listeners, whatever they are + moduleB.onReactionComplete(...) + moduleC.onReactionComplete(...) + end + + eventful.onProjItemCheckMovement[modId] = function(...) + moduleD.onProjItemCheckMovement(...) + end + + eventful.onProjUnitCheckMovement[modId] = function(...) + moduleD.onProjUnitCheckMovement(...) + end + + print("Example mod enabled") + elseif args[1] == "disable" then + -- Order: on unload, then cancel the callbacks in the same order as above + + moduleA.onUnload() + + repeatUtil.cancel(modId .. " 1 ticks") + repeatUtil.cancel(modId .. " 100 frames") + + eventful.onReactionComplete[modId] = nil + eventful.onProjItemCheckMovement[modId] = nil + eventful.onProjUnitCheckMovement[modId] = nil + + print("Example mod disabled") + elseif not args[1] then + dfhack.printerr("No argument given to example-mod/main") + else + dfhack.printerr("Unknown argument \"" .. args[1] .. "\" to example-mod/main") + end + +You can see there are four cases depending on arguments. Set up the callbacks and call on-load functions if enabled, dismantle the callbacks and call on-unload functions if disabled, no arguments given, and invalid argument(s) given. + +Here is an example of an ``raw/init.d/`` file: :: + + dfhack.run_command("example-mod/main enable") -- Very simple. Could be called "init-example-mod.lua" + +Here is what ``raw/scripts/module-a.lua`` would look like: :: + + --@ module = true + -- The above line is required for dfhack.reqscript to work + + function onLoad() -- global variables are exported + -- blah + end + + local function usedByOnTick() -- local variables are not exported + -- blah + end + + function onTick() -- exported + for blah in ipairs(blah) do + usedByOnTick() + end + end + +It is recommended to check `reqscript `'s documentation. ``reqscript`` caches scripts but will reload scripts that have changed (it checks the file's last modification date) so you can do live editing *and* have common tables et cetera between scripts that require the same module. From fcd8839c0d81da1e47ab7e2902caf91e27feaa97 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Fri, 15 Jul 2022 19:43:48 +0100 Subject: [PATCH 210/854] Wrap lines to 80 characters --- docs/guides/modding-guide.rst | 213 +++++++++++++++++++++++++--------- 1 file changed, 161 insertions(+), 52 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index aa07ca8a1..cebd34745 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -6,41 +6,70 @@ DFHack modding guide What is a mod/script? --------------------- -A script is a single file that can be run as a command in DFHack, like something that modifies or displays game data on request. A mod is something you install to get persistent behavioural changes in the game and/or add new content. DFHack mods contain and use scripts as well as often having a raw mod component. +A script is a single file that can be run as a command in DFHack, like something +that modifies or displays game data on request. A mod is something you install +to get persistent behavioural changes in the game and/or add new content. DFHack +mods contain and use scripts as well as often having a raw mod component. -DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at https://www.lua.org/pil/1.html. +DFHack scripts are written in Lua. If you don't already know Lua, there's a +great primer at https://www.lua.org/pil/1.html. Why not just use raw modding? ----------------------------- -For many things it's either completely and only (sensibly) doable in raws or completely and only doable with DFHack. For mods where DFHack is an alternative and not the only option, it's much less hacky, easier to maintain, and easier to extend, and is not prone to side-effects. A great example is adding a syndrome when a reaction is performed requiring an exploding boulder in raws but having dedicated tools for it if you use DFHack. Many things will require a mix of raw modding and DFHack. +For many things it's either completely and only (sensibly) doable in raws or +completely and only doable with DFHack. For mods where DFHack is an alternative +and not the only option, it's much less hacky, easier to maintain, and easier to +extend, and is not prone to side-effects. A great example is adding a syndrome +when a reaction is performed requiring an exploding boulder in raws but having +dedicated tools for it if you use DFHack. Many things will require a mix of raw +modding and DFHack. A mod-maker's development environment ------------------------------------- -Scripts can be run from a world's ``raw/scripts/`` directory, and (configurably) are run by default from ``hack/scripts/``. Scripts in ``raw/init.d/`` are automatically run on world load. Scripts within the raws are a component for more advanced mods. +Scripts can be run from a world's ``raw/scripts/`` directory, and (configurably) +are run by default from ``hack/scripts/``. Scripts in ``raw/init.d/`` are +automatically run on world load. Scripts within the raws are a component for +more advanced mods. -A script is run by writing its path and name from a script path folder without the file extension into a DFHack command prompt (in-game or the external one). E.g. ``gui/gm-editor`` for ``hack/scripts/gui/gm-editor.lua``. +A script is run by writing its path and name from a script path folder without +the file extension into a DFHack command prompt (in-game or the external one). +E.g. ``gui/gm-editor`` for ``hack/scripts/gui/gm-editor.lua``. -You can make all your scripts in ``hack/scripts/``, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like ``own-scripts`` and add it to ``dfhack-config/script-paths.txt``. You could also make a folder for external installed scripts from the internet that are not in ``hack/scripts/``. You can prepend your script paths entries with a ``+`` so that they take precedence over other folders. +You can make all your scripts in ``hack/scripts/``, but this is not recommended +as it makes things much harder to maintain each update. It's recommended to make +a folder with a name like ``own-scripts`` and add it to +``dfhack-config/script-paths.txt``. You could also make a folder for external +installed scripts from the internet that are not in ``hack/scripts/``. You can +prepend your script paths entries with a ``+`` so that they take precedence over +other folders. -If your mod is installed into ``raw/scripts/`` be aware that the copies of the scripts in ``data/save/*/raw/`` are checked first and will run instead of any changes you make to an in-development copy outside of a raw folder. +If your mod is installed into ``raw/scripts/`` be aware that the copies of the +scripts in ``data/save/*/raw/`` are checked first and will run instead of any +changes you make to an in-development copy outside of a raw folder. The structure of the game ------------------------- -"The game" is in the global variable `df `. The game's memory can be found in ``df.global``, containing things like the list of all items, whether to reindex pathfinding, et cetera. Also relevant to us in ``df`` are the various types found in the game, e.g. ``df.pronoun_type`` which we will be using. +"The game" is in the global variable `df `. The game's memory can be +found in ``df.global``, containing things like the list of all items, whether to +reindex pathfinding, et cetera. Also relevant to us in ``df`` are the various +types found in the game, e.g. ``df.pronoun_type`` which we will be using. Your first script ----------------- -So! It's time to write your first script. We are going to make a script that will get the pronoun type of the currently selected unit (there are many contexts where the function that gets the currently selected unit works). +So! It's time to write your first script. We are going to make a script that +will get the pronoun type of the currently selected unit (there are many +contexts where the function that gets the currently selected unit works). First line, we get the unit. :: local unit = dfhack.gui.getSelectedUnit() -If no unit is selected, an error message will be printed (which can be silenced by passing ``true`` to ``getSelectedUnit``) and ``unit`` will be ``nil``. +If no unit is selected, an error message will be printed (which can be silenced +by passing ``true`` to ``getSelectedUnit``) and ``unit`` will be ``nil``. If ``unit`` is ``nil``, we don't want the script to run anymore. :: @@ -48,36 +77,60 @@ If ``unit`` is ``nil``, we don't want the script to run anymore. :: return end -Now, the field ``sex`` in a unit is an integer, but each integer corresponds to a string value (it, she, or he). We get this value by indexing the bidirectional map ``df.pronoun_type`` with an integer from the unit. Indexing the other way, with one of the strings, will yield its corresponding number. So: :: +Now, the field ``sex`` in a unit is an integer, but each integer corresponds to +a string value (it, she, or he). We get this value by indexing the bidirectional +map ``df.pronoun_type`` with an integer from the unit. Indexing the other way, +with one of the strings, will yield its corresponding number. So: :: local pronounTypeString = df.pronoun_type[unit.sex] print(pronounTypeString) -Simple. Save this as a Lua file in your own scripts directory and run it as shown before when focused on a unit one way or another. +Simple. Save this as a Lua file in your own scripts directory and run it as +shown before when focused on a unit one way or another. Getting used to gm-editor and df-structures exploration ------------------------------------------------------- -So how could you have known about the field and type we just used? Well, there are two main tools for discovering the various fields in the game's data structures. The first is the ``df-structures`` repository (at https://github.com/DFHack/df-structures) that contains XML files denoting the contents of the game's structures. The second is the script ``gui/gm-editor`` which is an interactive data explorer. You can run the script while objects like units are selected to view the data within them. You can also pass ``scr`` as an argument to the script to view the data for the current screen. Press ? while the script is active to view help. +So how could you have known about the field and type we just used? Well, there +are two main tools for discovering the various fields in the game's data +structures. The first is the ``df-structures`` repository +(at https://github.com/DFHack/df-structures) that contains XML files denoting +the contents of the game's structures. The second is the script +``gui/gm-editor`` which is an interactive data explorer. You can run the script +while objects like units are selected to view the data within them. You can also +pass ``scr`` as an argument to the script to view the data for the current +screen. Press ? while the script is active to view help. -Familiarising yourself with the many structs of the game will help with ideas immensely, and you can always ask for help in the right places (e.g. DFHack's Discord). +Familiarising yourself with the many structs of the game will help with ideas +immensely, and you can always ask for help in the right places (e.g. DFHack's +Discord). Detecting triggers ------------------ -One main method for getting new behaviour into the game is callback functions. There are two main libraries for this, ``repeat-util`` and ``eventful``. ``repeat-util`` is used to run a function once per configurable number of frames (paused or unpaused), ticks (unpaused), in-game days, months, or years. For adding behaviour you will most often want something to run once a tick. ``eventful`` is used to get code to run (with special parameters!) when something happens in the game, like a reaction or job being completed or a projectile moving. +One main method for getting new behaviour into the game is callback functions. +There are two main libraries for this, ``repeat-util`` and ``eventful``. +``repeat-util`` is used to run a function once per configurable number of frames +(paused or unpaused), ticks (unpaused), in-game days, months, or years. For +adding behaviour you will most often want something to run once a tick. +``eventful`` is used to get code to run (with special parameters!) when +something happens in the game, like a reaction or job being completed or a +projectile moving. -To get something to run once per tick, we would want to call ``repeat-util``'s ``scheduleEvery`` function. +To get something to run once per tick, we would want to call ``repeat-util``'s +``scheduleEvery`` function. First, we load the module: :: local repeatUtil = require("repeat-util") -Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. It's recommended to use something like a mod id. :: +Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. +It's recommended to use something like a mod id. :: local modId = "callback-example-mod" -Then, we pass the key, amount of time units between function calls, what the time units are, and finally the callback function itself: :: +Then, we pass the key, amount of time units between function calls, what the +time units are, and finally the callback function itself: :: repeatUtil.scheduleEvery(modId, 1, "ticks", function() -- Do something like iterating over all active units @@ -90,20 +143,29 @@ Then, we pass the key, amount of time units between function calls, what the tim local eventful = require("plugins.eventful") -``eventful`` contains a table for each event which you populate with functions. Each function in the table is then called with the appropriate arguments when the event occurs. So, for example, to print the position of a moving (item) projectile: :: +``eventful`` contains a table for each event which you populate with functions. +Each function in the table is then called with the appropriate arguments when +the event occurs. So, for example, to print the position of a moving (item) +projectile: :: eventful.onProjItemCheckMovement[modId] = function(projectile) print(projectile.cur_pos.x, projectile.cur_pos.y, projectile.cur_pos.z) end -Check the full list of events at https://docs.dfhack.org/en/stable/docs/Lua%20API.html#list-of-events. +Check the full list of events at +https://docs.dfhack.org/en/stable/docs/Lua%20API.html#list-of-events. Custom raw tokens ----------------- -In this section, we are going to use `custom raw tokens ` applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how you could make boots that make units go faster when worn. Both of these involve custom raw tokens. +In this section, we are going to use `custom raw tokens ` +applied to a reaction to transfer the material of a reagent to a product as a +handle improvement (like on artifact buckets), and then we are going to see how +you could make boots that make units go faster when worn. Both of these involve +custom raw tokens. -First, let's define a custom crossbow with its own custom reaction. The crossbow: :: +First, let's define a custom crossbow with its own custom reaction. The +crossbow: :: [ITEM_WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE] [NAME:crossbow:crossbows] @@ -119,7 +181,8 @@ First, let's define a custom crossbow with its own custom reaction. The crossbow [ATTACK_PREPARE_AND_RECOVER:3:3] [SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER:2] custom token (you'll see) -The reaction to make it (you would add the reaction and not the weapon to an entity raw): :: +The reaction to make it (you would add the reaction and not the weapon to an +entity raw): :: [REACTION:MAKE_SIEGE_CROSSBOW] [NAME:make siege crossbow] @@ -132,10 +195,13 @@ The reaction to make it (you would add the reaction and not the weapon to an ent [ANY_PLANT_MATERIAL] [REAGENT:handle 2:1:BLOCKS:NONE:NONE:NONE] [ANY_PLANT_MATERIAL] - [SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] another custom token + [SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] + another custom token [PRODUCT:100:1:WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE:GET_MATERIAL_FROM_REAGENT:bar:NONE] -So, we are going to use the ``eventful`` module to make it so that (after the script is run) when this crossbow is crafted, it will have two handles, each with the material given by the block reagents. +So, we are going to use the ``eventful`` module to make it so that (after the +script is run) when this crossbow is crafted, it will have two handles, each +with the material given by the block reagents. First, require the modules we are going to use. :: @@ -145,15 +211,20 @@ First, require the modules we are going to use. :: Now, let's make a callback: :: local modId = "siege-crossbow-mod" - eventful.onReactionComplete[modId] = function(reaction, reactionProduct, unit, inputItems, inputReagents, outputItems) + eventful.onReactionComplete[modId] = function(reaction, reactionProduct, + unit, inputItems, inputReagents, outputItems) -First, we check to see if it the reaction that just happened is relevant to this callback: :: +First, we check to see if it the reaction that just happened is relevant to this +callback: :: - if not customRawTokens.getToken(reaction, "SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") then + if not customRawTokens.getToken(reaction, + "SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") + then return end -Then, we get the product number listed. Next, for every reagent, if the reagent name starts with "handle" then we get the corresponding item, and... :: +Then, we get the product number listed. Next, for every reagent, if the reagent +name starts with "handle" then we get the corresponding item, and... :: for i, reagent in ipairs(inputReagents) do if reagent.code:sub(1, #"handle") == "handle" then @@ -167,9 +238,9 @@ Then, we get the product number listed. Next, for every reagent, if the reagent -- new.maker = outputItems[0].maker -- not a typical improvement new.type = df.itemimprovement_specific_type.HANDLE outputItems[productNumber - 1].improvements:insert("#", new) - -- break -- multiple handles, multiple "the handle is made from"s, so no break -It's all a bit loose and hacky but it works, at least if you don't have multiple stacks filling up one reagent. +It's all a bit loose and hacky but it works, at least if you don't have multiple +stacks filling up one reagent. Let's also make some code to modify the fire rate of the siege crossbow. :: @@ -189,10 +260,12 @@ Let's also make some code to modify the fire rate of the siege crossbow. :: end local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER")) or 1 - firer.counters.think_counter = math.floor(firer.counters.think_counter * multiplier) + firer.counters.think_counter = math.floor(firer.counters.think_counter * + multiplier) end -Now, let's see how we could make some "pegasus boots". First, let's define the item in the raws: :: +Now, let's see how we could make some "pegasus boots". First, let's define the +item in the raws: :: [ITEM_SHOES:ITEM_SHOES_BOOTS_PEGASUS] [NAME:pegasus boot:pegasus boots] @@ -207,13 +280,16 @@ Now, let's see how we could make some "pegasus boots". First, let's define the i [METAL] [LEATHER] [HARD] - [PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token (you don't have to comment this every time) + [PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token + (you don't have to comment this every time) Then, let's make a ``repeat-util`` callback for once a tick: :: repeatUtil.scheduleEvery(modId, 1, "ticks", function() -Let's iterate over every active unit, and for every unit, initialise a variable for how much we are going to take from their movement timer and iterate over all their worn items: :: +Let's iterate over every active unit, and for every unit, initialise a variable +for how much we are going to take from their movement timer and iterate over all +their worn items: :: for _, unit in ipairs(df.global.world.units.active) do local amount = 0 @@ -225,25 +301,44 @@ Now, we will add up the effect of all speed-increasing gear and apply it: :: amount = amount + tonumber((customRawTokens.getToken(entry.item, "PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK")) or 0) end end - dfhack.units.addMoveTimer(-amount) -- Subtract amount from movement timer if currently moving + dfhack.units.addMoveTimer(-amount) -- Subtract amount from movement timer if + currently moving The structure of a full mod --------------------------- -Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. Alternatively you can create multiple callbacks with your mod ID being a prefix, though this way there is no guarantee about the order if that is important. You will have to use your mod ID as a prefix if you register multiple ``repeat-util`` callbacks, though. +Now, you may have noticed that you won't be able to run multiple functions on +tick/as event callbacks with that ``modId`` idea alone. To solve that we can +just define all the functions we want and call them from a single function. +Alternatively you can create multiple callbacks with your mod ID being a prefix, +though this way there is no guarantee about the order if that is important. You +will have to use your mod ID as a prefix if you register multiple +``repeat-util`` callbacks, though. -Create a folder for mod projects somewhere (e.g. ``hack/my-scripts/mods/``, or maybe somewhere outside your Dwarf Fortress installation) and use your mod ID (in hyphen-case) as the name for the mod folders within it. The structure of and environment for fully-functioning modular mods are as follows: +Create a folder for mod projects somewhere (e.g. ``hack/my-scripts/mods/``, or +maybe somewhere outside your Dwarf Fortress installation) and use your mod ID +(in hyphen-case) as the name for the mod folders within it. The structure of and +environment for fully-functioning modular mods are as follows: * The main content of the mod would be in the ``raw`` folder: - * A Lua file in ``raw/init.d/`` to initialise the mod by calling ``your-mod-id/main/ enable``. + * A Lua file in ``raw/init.d/`` to initialise the mod by calling + ``your-mod-id/main/ enable``. * Raw content (potentially with custom raw tokens) in ``raw/objects/``. - * A subfolder for your mod in ``raw/scripts/`` containing a ``main.lua`` file (an example of which we will see) and all the modules containing the functions used in callbacks to ``repeat-util`` and ``eventful``. Potentially a file containing constant definitions used by your mod (perhaps defined by the game, like the acceleration of parabolic projectiles due to gravity (``4900``)) too. + * A subfolder for your mod in ``raw/scripts/`` containing a ``main.lua`` file + (an example of which we will see) and all the modules containing the functions + used in callbacks to ``repeat-util`` and ``eventful``. Potentially a file + containing constant definitions used by your mod (perhaps defined by the + game, like the acceleration of parabolic projectiles due to gravity + (``4900``)) too. * Using git within each mod folder is recommended, but not required. * A ``readme.md`` markdown file is also recommended. -* An ``addToEntity.txt`` file containing lines to add to entity definitions for access to mod content would be needed if applicable. -* Unless you want to merge your ``raw`` folder with your worlds every time you make a change to your scripts, you should add ``path/to/your-mod/raw/scripts/`` to your script paths. +* An ``addToEntity.txt`` file containing lines to add to entity definitions for + access to mod content would be needed if applicable. +* Unless you want to merge your ``raw`` folder with your worlds every time you + make a change to your scripts, you should add + ``path/to/your-mod/raw/scripts/`` to your script paths. Now, let's take a look at an example ``main.lua`` file. :: @@ -256,13 +351,19 @@ Now, let's take a look at an example ``main.lua`` file. :: if args[1] == "enable" then -- The modules and what they link into the environment with -- Each module exports functions named the way they are to be used - local moduleA = dfhack.reqscript("example-mod/module-a") -- on load, every tick - local moduleB = dfhack.reqscript("example-mod/module-b") -- on load, on unload, onReactionComplete - local moduleC = dfhack.reqscript("example-mod/module-c") -- onReactionComplete - local moduleD = dfhack.reqscript("example-mod/module-d") -- every 100 frames, onProjItemCheckMovement, onProjUnitCheckMovement + local moduleA = dfhack.reqscript("example-mod/module-a") -- on load, + -- every tick + local moduleB = dfhack.reqscript("example-mod/module-b") -- on load, + -- on unload, onReactionComplete + local moduleC = dfhack.reqscript("example-mod/module-c") + -- onReactionComplete + local moduleD = dfhack.reqscript("example-mod/module-d") -- every 100 + -- frames, onProjItemCheckMovement, onProjUnitCheckMovement -- Set up the modules - -- Order: on load, repeat-util ticks (from smallest interval to largest), days, months, years, and frames, then eventful callbacks in the same order as the first modules to use them + -- Order: on load, repeat-util ticks (from smallest interval to + -- largest), days, months, years, and frames, then eventful callbacks in + -- the same order as the first modules to use them moduleA.onLoad() moduleB.onLoad() @@ -291,7 +392,8 @@ Now, let's take a look at an example ``main.lua`` file. :: print("Example mod enabled") elseif args[1] == "disable" then - -- Order: on unload, then cancel the callbacks in the same order as above + -- Order: on unload, then cancel the callbacks in the same order as + -- above moduleA.onUnload() @@ -306,14 +408,18 @@ Now, let's take a look at an example ``main.lua`` file. :: elseif not args[1] then dfhack.printerr("No argument given to example-mod/main") else - dfhack.printerr("Unknown argument \"" .. args[1] .. "\" to example-mod/main") + dfhack.printerr("Unknown argument \"" .. args[1] .. + "\" to example-mod/main") end -You can see there are four cases depending on arguments. Set up the callbacks and call on-load functions if enabled, dismantle the callbacks and call on-unload functions if disabled, no arguments given, and invalid argument(s) given. +You can see there are four cases depending on arguments. Set up the callbacks +and call on load functions if enabled, dismantle the callbacks and call on +unload functions if disabled, no arguments given, and invalid argument(s) given. Here is an example of an ``raw/init.d/`` file: :: - dfhack.run_command("example-mod/main enable") -- Very simple. Could be called "init-example-mod.lua" + dfhack.run_command("example-mod/main enable") -- Very simple. Could be + -- called "init-example-mod.lua" Here is what ``raw/scripts/module-a.lua`` would look like: :: @@ -334,4 +440,7 @@ Here is what ``raw/scripts/module-a.lua`` would look like: :: end end -It is recommended to check `reqscript `'s documentation. ``reqscript`` caches scripts but will reload scripts that have changed (it checks the file's last modification date) so you can do live editing *and* have common tables et cetera between scripts that require the same module. +It is recommended to check `reqscript `'s documentation. +``reqscript`` caches scripts but will reload scripts that have changed (it +checks the file's last modification date) so you can do live editing *and* have +common tables et cetera between scripts that require the same module. From 7cf5a7dac90a5675c90336e67de56dad94d22aa9 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Fri, 15 Jul 2022 19:44:21 +0100 Subject: [PATCH 211/854] main.lua --> raw/scripts/main.lua --- docs/guides/modding-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index cebd34745..e35422aef 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -340,7 +340,7 @@ environment for fully-functioning modular mods are as follows: make a change to your scripts, you should add ``path/to/your-mod/raw/scripts/`` to your script paths. -Now, let's take a look at an example ``main.lua`` file. :: +Now, let's take a look at an example ``raw/scripts/main.lua`` file. :: local repeatUtil = require("repeat-util") local eventful = require("plugins.eventful") From 9b340a76306e2b5aaebaf83fcd787c3ecf81502a Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 15 Jul 2022 13:46:01 -0700 Subject: [PATCH 212/854] support submit2 for EditFields --- docs/Lua API.rst | 1 + library/lua/gui/widgets.lua | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index fb2b18291..6ebdce99c 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3882,6 +3882,7 @@ Attributes: If it returns false, the character is ignored. :on_change: Change notification callback; used as ``on_change(new_text,old_text)``. :on_submit: Enter key callback; if set the field will handle the key and call ``on_submit(text)``. +:on_submit2: Shift-Enter key callback; if set the field will handle the key and call ``on_submit2(text)``. :key: If specified, the field is disabled until this key is pressed. Must be given as a string. :key_sep: If specified, will be used to customize how the activation key is displayed. See ``token.key_sep`` in the ``Label`` documentation below. diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index d5d94ffd9..07ce3422d 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -184,6 +184,7 @@ EditField.ATTRS{ on_char = DEFAULT_NIL, on_change = DEFAULT_NIL, on_submit = DEFAULT_NIL, + on_submit2 = DEFAULT_NIL, key = DEFAULT_NIL, key_sep = DEFAULT_NIL, frame = {h=1}, @@ -253,6 +254,17 @@ function EditField:onInput(keys) return not not self.key end + if keys.SEC_SELECT then + if self.key then + self:setFocus(false) + end + if self.on_submit2 then + self.on_submit2(self.text) + return true + end + return not not self.key + end + if keys._STRING then local old = self.text if keys._STRING == 0 then From 585f6aad333557153be659c2466068dd3e3eaf46 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 15 Jul 2022 15:45:03 -0700 Subject: [PATCH 213/854] fix extra space within bold segment for keybindings --- conf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conf.py b/conf.py index 461fa789b..831833e26 100644 --- a/conf.py +++ b/conf.py @@ -69,7 +69,8 @@ def dfhack_keybind_role_func(role, rawtext, text, lineno, inliner, for cmd, key, ctx in KEYBINDS[text]: n = nodes.paragraph() newnode += n - n += nodes.strong('Keybinding: ', 'Keybinding: ') + n += nodes.strong('Keybinding:', 'Keybinding:') + n += nodes.inline(' ', ' ') for k in key: n += nodes.inline(k, k, classes=['kbd']) if cmd != text: From 27425e47f633356135a21a74a6f5f59a1002e4ea Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 15 Jul 2022 22:22:51 -0700 Subject: [PATCH 214/854] support cursor movement in EditFields --- docs/Lua API.rst | 7 ++++ library/lua/gui/widgets.lua | 77 +++++++++++++++++++++++++++++++------ 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 6ebdce99c..e884383d5 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3904,6 +3904,13 @@ and then call the ``on_submit`` callback. Pressing the Escape key will also release keyboard focus, but first it will restore the text that was displayed before the ``EditField`` gained focus and then call the ``on_change`` callback. +The ``EditField`` cursor can be moved to where you want to insert/remove text. +The following cursor movement keys are recognized: + +- Left/Right arrow: move the cursor one character to the left or right. +- Ctrl-Left/Right arrow: move the cursor one word to the left or right. +- Alt-Left/Right arrow: move the cursor to the beginning/end of the text. + Label class ----------- diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 07ce3422d..d09bdf9cd 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -197,6 +197,8 @@ function EditField:init() self:setFocus(true) end + self.cursor = 1 + self:addviews{HotkeyLabel{frame={t=0,l=0}, key=self.key, key_sep=self.key_sep, @@ -208,6 +210,19 @@ function EditField:getPreferredFocusState() return not self.key end +function EditField:setCursor(cursor) + if not cursor or cursor > #self.text then + self.cursor = #self.text + 1 + return + end + self.cursor = math.max(1, cursor) +end + +function EditField:setText(text, cursor) + self.text = text + self:setCursor(cursor) +end + function EditField:postUpdateLayout() self.text_offset = self.subviews[1]:getTextWidth() end @@ -215,14 +230,29 @@ end function EditField:onRenderBody(dc) dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0) - local cursor = '_' + local cursor_char = '_' if not self.active or not self.focus or gui.blink_visible(300) then - cursor = ' ' + cursor_char = (self.cursor > #self.text) and ' ' or + self.text:sub(self.cursor, self.cursor) end - local txt = self.text .. cursor + local txt = self.text:sub(1, self.cursor - 1) .. cursor_char .. + self.text:sub(self.cursor + 1) local max_width = dc.width - self.text_offset if #txt > max_width then - txt = string.char(27)..string.sub(txt, #txt-max_width+2) + -- get the substring in the vicinity of the cursor + max_width = max_width - 2 + local half_width = math.floor(max_width/2) + local start_pos = math.max(1, self.cursor-half_width) + local end_pos = math.min(#txt, self.cursor+half_width-1) + if self.cursor + half_width > #txt then + start_pos = #txt - max_width + end + if self.cursor - half_width <= 1 then + end_pos = max_width + 1 + end + txt = ('%s%s%s'):format(start_pos == 1 and '' or string.char(27), + txt:sub(start_pos, end_pos), + end_pos == #txt and '' or string.char(26)) end dc:advance(self.text_offset):string(txt) end @@ -252,9 +282,7 @@ function EditField:onInput(keys) return true end return not not self.key - end - - if keys.SEC_SELECT then + elseif keys.SEC_SELECT then if self.key then self:setFocus(false) end @@ -263,17 +291,42 @@ function EditField:onInput(keys) return true end return not not self.key - end - - if keys._STRING then + elseif keys.CURSOR_LEFT then + self.cursor = math.max(1, self.cursor - 1) + return true + elseif keys.A_MOVE_W_DOWN then -- Ctrl-Left (prev word start) + local _, prev_word_start = self.text:sub(1, self.cursor-1): + find('.*[^%w_%-]+[%w_%-]') + self.cursor = prev_word_start or 1 + return true + elseif keys.A_CARE_MOVE_W then -- Alt-Left (home) + self.cursor = 1 + return true + elseif keys.CURSOR_RIGHT then + self.cursor = math.min(self.cursor + 1, #self.text + 1) + return true + elseif keys.A_MOVE_E_DOWN then -- Ctrl-Right (next word end) + local _, next_word_end = self.text:find('[%w_%-]+[^%w_%-]', self.cursor) + self.cursor = next_word_end or #self.text + 1 + return true + elseif keys.A_CARE_MOVE_E then -- Alt-Right (end) + self.cursor = #self.text + 1 + return true + elseif keys._STRING then local old = self.text if keys._STRING == 0 then -- handle backspace - self.text = string.sub(old, 1, #old-1) + local del_pos = self.cursor - 1 + if del_pos > 0 then + self.text = old:sub(1, del_pos-1) .. old:sub(del_pos+1) + self.cursor = del_pos + end else local cv = string.char(keys._STRING) if not self.on_char or self.on_char(cv, old) then - self.text = old .. cv + self.text = old:sub(1, self.cursor-1) .. cv .. + old:sub(self.cursor) + self.cursor = self.cursor + 1 end end if self.on_change and self.text ~= old then From f65f36ffee84e5671b96249426d1e6724e898357 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 16 Jul 2022 22:03:39 -0700 Subject: [PATCH 215/854] move the cursor in an EditField on mouse lclick --- library/lua/gui/widgets.lua | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index d09bdf9cd..2dc197064 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -265,7 +265,7 @@ function EditField:onInput(keys) if self.key and keys.LEAVESCREEN then local old = self.text - self.text = self.saved_text + self:setText(self.saved_text) if self.on_change and old ~= self.saved_text then self.on_change(self.text, old) end @@ -291,26 +291,32 @@ function EditField:onInput(keys) return true end return not not self.key + elseif keys._MOUSE_L then + local mouse_x, mouse_y = self:getMousePos() + if mouse_x then + self:setCursor(mouse_x) + return true + end elseif keys.CURSOR_LEFT then - self.cursor = math.max(1, self.cursor - 1) + self:setCursor(self.cursor - 1) return true elseif keys.A_MOVE_W_DOWN then -- Ctrl-Left (prev word start) local _, prev_word_start = self.text:sub(1, self.cursor-1): find('.*[^%w_%-]+[%w_%-]') - self.cursor = prev_word_start or 1 + self:setCursor(prev_word_start or 1) return true elseif keys.A_CARE_MOVE_W then -- Alt-Left (home) - self.cursor = 1 + self:setCursor(1) return true elseif keys.CURSOR_RIGHT then - self.cursor = math.min(self.cursor + 1, #self.text + 1) + self:setCursor(self.cursor + 1) return true elseif keys.A_MOVE_E_DOWN then -- Ctrl-Right (next word end) local _, next_word_end = self.text:find('[%w_%-]+[^%w_%-]', self.cursor) - self.cursor = next_word_end or #self.text + 1 + self:setCursor(next_word_end) return true elseif keys.A_CARE_MOVE_E then -- Alt-Right (end) - self.cursor = #self.text + 1 + self:setCursor() return true elseif keys._STRING then local old = self.text @@ -318,15 +324,14 @@ function EditField:onInput(keys) -- handle backspace local del_pos = self.cursor - 1 if del_pos > 0 then - self.text = old:sub(1, del_pos-1) .. old:sub(del_pos+1) - self.cursor = del_pos + self:setText(old:sub(1, del_pos-1) .. old:sub(del_pos+1), + del_pos) end else local cv = string.char(keys._STRING) if not self.on_char or self.on_char(cv, old) then - self.text = old:sub(1, self.cursor-1) .. cv .. - old:sub(self.cursor) - self.cursor = self.cursor + 1 + self:setText(old:sub(1,self.cursor-1)..cv..old:sub(self.cursor), + self.cursor + 1) end end if self.on_change and self.text ~= old then From 805456e82b568d5601a14e643f8b09040bd66659 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 16 Jul 2022 22:18:38 -0700 Subject: [PATCH 216/854] allow mouse lclick to select a List item --- library/lua/gui/widgets.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 2dc197064..d6505d862 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -1019,6 +1019,15 @@ function List:onInput(keys) elseif self.on_submit2 and keys.SEC_SELECT then self:submit2() return true + elseif keys._MOUSE_L then + local _, mouse_y = self:getMousePos() + if mouse_y and #self.choices > 0 and + mouse_y < (#self.choices-self.page_top+1) * self.row_height then + local idx = self.page_top + math.floor(mouse_y/self.row_height) + self:setSelected(idx) + self:submit() + return true + end else for k,v in pairs(self.scroll_keys) do if keys[k] then From f35420072837b32e6a2f563b11c43c6fbc32cbbb Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 16 Jul 2022 22:23:22 -0700 Subject: [PATCH 217/854] update widget docs --- docs/Lua API.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index e884383d5..107a0ba2e 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3904,8 +3904,9 @@ and then call the ``on_submit`` callback. Pressing the Escape key will also release keyboard focus, but first it will restore the text that was displayed before the ``EditField`` gained focus and then call the ``on_change`` callback. -The ``EditField`` cursor can be moved to where you want to insert/remove text. -The following cursor movement keys are recognized: +The ``EditField`` cursor can be moved to where you want to insert/remove text +by clicking the mouse at that position. In addition, the following cursor +movement keys are recognized: - Left/Right arrow: move the cursor one character to the left or right. - Ctrl-Left/Right arrow: move the cursor one word to the left or right. @@ -4135,10 +4136,10 @@ It has the following attributes: :on_select: Selection change callback; called as ``on_select(index,choice)``. This is also called with *nil* arguments if ``setChoices`` is called with an empty list. -:on_submit: Enter key callback; if specified, the list reacts to the key - and calls it as ``on_submit(index,choice)``. -:on_submit2: Shift-Enter key callback; if specified, the list reacts to the key - and calls it as ``on_submit2(index,choice)``. +:on_submit: Enter key or mouse click callback; if specified, the list calls it + as ``on_submit(index,choice)``. +:on_submit2: Shift-Enter key callback; if specified, the list calls it as + ``on_submit2(index,choice)``. :row_height: Height of every row in text lines. :icon_width: If not *nil*, the specified number of character columns are reserved to the left of the list item for the icons. From 28161ed63d11a4ec7b4bb0263801015894357ec3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 17 Jul 2022 09:12:20 -0700 Subject: [PATCH 218/854] add command frequency file based on survey results used for ordering launcher autocomplete results --- dfhack-config/command_counts.json | 145 ++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 dfhack-config/command_counts.json diff --git a/dfhack-config/command_counts.json b/dfhack-config/command_counts.json new file mode 100644 index 000000000..6acfda21b --- /dev/null +++ b/dfhack-config/command_counts.json @@ -0,0 +1,145 @@ +{ + "manipulator": 75, + "autolabor": 59, + "reveal": 51, + "help": 50, + "ls": 50, + "die": 50, + "tags": 50, + "embark-assistant": 42, + "prospect": 37, + "autodump": 36, + "clean": 35, + "gui/workflow": 28, + "workflow": 28, + "exportlegends": 26, + "gui/autobutcher": 25, + "autobutcher": 25, + "digv": 24, + "fastdwarf": 22, + "autonestbox": 20, + "showmood": 19, + "gui/liquids": 18, + "liquids": 18, + "search": 18, + "gui/quickfort": 15, + "quickfort": 15, + "createitem": 14, + "stocks": 14, + "autofarm": 12, + "autochop": 12, + "tiletypes": 12, + "exterminate": 12, + "buildingplan": 12, + "quicksave": 11, + "gui/gm-editor": 11, + "cleanowned": 10, + "gui/autogems": 9, + "autogems": 9, + "stonesense": 9, + "gui/stockpiles": 8, + "stockpiles": 8, + "changevein": 8, + "gui/teleport": 7, + "teleport": 7, + "seedwatch": 6, + "automelt": 6, + "embark-tools": 6, + "cursecheck": 5, + "open-legends": 5, + "ban-cooking": 5, + "burial": 5, + "automaterial": 5, + "remove-stress": 5, + "gui/blueprint": 5, + "blueprint": 5, + "tailor": 4, + "startdwarf": 4, + "3dveins": 4, + "digcircle": 4, + "nestboxes": 3, + "deathcause": 3, + "list-agreements": 3, + "gui/room-list": 3, + "points": 3, + "region-pops": 3, + "gui/advfort": 3, + "unsuspend": 3, + "locate-ore": 3, + "changelayer": 3, + "source": 3, + "gui/gm-unit": 3, + "combine-drinks": 3, + "combine-plants": 3, + "deteriorate": 3, + "warn-starving": 3, + "gaydar": 2, + "gui/dfstatus": 2, + "gui/rename": 2, + "rename": 2, + "fix-ster": 2, + "job-material": 2, + "stockflow": 2, + "drain-aquifer": 2, + "full-heal": 2, + "spawnunit": 2, + "flashstep": 2, + "gui/family-affairs": 2, + "caravan": 2, + "mousequery": 2, + "tweak": 2, + "confirm": 2, + "autoclothing": 1, + "autounsuspend": 1, + "prioritize": 1, + "dwarfmonitor": 1, + "show-unit-syndromes": 1, + "troubleshoot-item": 1, + "gui/mechanisms": 1, + "gui/pathable": 1, + "hotkeys": 1, + "infiniteSky": 1, + "force": 1, + "hermit": 1, + "strangemood": 1, + "weather": 1, + "add-recipe": 1, + "autotrade": 1, + "zone": 1, + "autonick": 1, + "stripcaged": 1, + "unforbid": 1, + "workorder": 1, + "gui/mod-manager": 1, + "spotclean": 1, + "plant": 1, + "regrass": 1, + "dig-now": 1, + "build-now": 1, + "clear-webs": 1, + "gui/siege-engine": 1, + "assign-skills": 1, + "brainwash": 1, + "elevate-mental": 1, + "elevate-physical": 1, + "launch": 1, + "linger": 1, + "make-legendary": 1, + "rejuvenate": 1, + "resurrect-adv": 1, + "questport": 1, + "getplants": 1, + "gui/stamper": 1, + "tweak": 1, + "fixveins": 1, + "deramp": 1, + "fix/dead-units": 1, + "fix/fat-dwarves": 1, + "fix/loyaltycascade": 1, + "fix/retrieve-units": 1, + "tweak": 1, + "sort-items": 1, + "gui/color-schemes": 1, + "color-schemes": 1, + "season-palette": 1 +} From 5723b76585985fff5bef29a2b2702dccbb42b36d Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 17 Jul 2022 15:43:58 -0700 Subject: [PATCH 219/854] click to correct cursor position on long strings where the left side of the string has been trimmed --- library/lua/gui/widgets.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index d6505d862..f4573a185 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -197,6 +197,7 @@ function EditField:init() self:setFocus(true) end + self.start_pos = 1 self.cursor = 1 self:addviews{HotkeyLabel{frame={t=0,l=0}, @@ -238,6 +239,7 @@ function EditField:onRenderBody(dc) local txt = self.text:sub(1, self.cursor - 1) .. cursor_char .. self.text:sub(self.cursor + 1) local max_width = dc.width - self.text_offset + self.start_pos = 1 if #txt > max_width then -- get the substring in the vicinity of the cursor max_width = max_width - 2 @@ -245,11 +247,12 @@ function EditField:onRenderBody(dc) local start_pos = math.max(1, self.cursor-half_width) local end_pos = math.min(#txt, self.cursor+half_width-1) if self.cursor + half_width > #txt then - start_pos = #txt - max_width + start_pos = #txt - (max_width - 1) end if self.cursor - half_width <= 1 then end_pos = max_width + 1 end + self.start_pos = start_pos > 1 and start_pos - 1 or start_pos txt = ('%s%s%s'):format(start_pos == 1 and '' or string.char(27), txt:sub(start_pos, end_pos), end_pos == #txt and '' or string.char(26)) @@ -294,7 +297,7 @@ function EditField:onInput(keys) elseif keys._MOUSE_L then local mouse_x, mouse_y = self:getMousePos() if mouse_x then - self:setCursor(mouse_x) + self:setCursor(self.start_pos + mouse_x) return true end elseif keys.CURSOR_LEFT then From 7f302888ec0b5b8a16185d9e507fca431c7d5454 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 17 Jul 2022 16:04:36 -0700 Subject: [PATCH 220/854] make HotkeyLabels react to clicking also be better about initializing EditField frame height --- library/lua/gui/widgets.lua | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index f4573a185..aea62bf87 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -187,10 +187,14 @@ EditField.ATTRS{ on_submit2 = DEFAULT_NIL, key = DEFAULT_NIL, key_sep = DEFAULT_NIL, - frame = {h=1}, modal = false, } +function EditField:preinit(init_table) + local frame = init_table.frame or {} + frame.h = frame.h or 1 +end + function EditField:init() local function on_activate() self.saved_text = self.text @@ -744,6 +748,16 @@ function HotkeyLabel:init() on_activate=self.on_activate}} end +function HotkeyLabel:onInput(keys) + if HotkeyLabel.super.onInput(self, keys) then + return true + elseif keys._MOUSE_L and self:getMousePos() then + self.on_activate() + return true + end + +end + ---------------------- -- CycleHotkeyLabel -- ---------------------- From e650bd094292ab28810b457aec3f9a9c04a78cfc Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 17 Jul 2022 17:05:29 -0700 Subject: [PATCH 221/854] add comment about plugin docs --- library/LuaApi.cpp | 5 ++++- library/include/PluginManager.h | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 649ceb2dc..c332933a2 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -3133,7 +3133,10 @@ static int internal_getCommandHelp(lua_State *L) { help += "."; } - help += "\n" + pc.usage; + if (pc.usage.size()) + { + help += "\n" + pc.usage; + } lua_pushstring(L, help.c_str()); return 1; } diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index f168d7e47..79b7e0492 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -97,6 +97,10 @@ namespace DFHack /// create a command with a name, description, function pointer to its code /// and saying if it needs an interactive terminal /// Most commands shouldn't require an interactive terminal! + /// Note that the description and usage fields are only used for + /// out-of-tree plugins that do not have rendered help installed in + /// the hack/docs directory. Help for all internal plugins comes from + /// the rendered .rst files. PluginCommand(const char * _name, const char * _description, command_function function_, From 301c8e93a19a515a720a69c137a85e7640932290 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 10:58:35 -0700 Subject: [PATCH 222/854] move builtin docs to individual files --- conf.py | 3 + docs/Builtin.rst | 280 ------------------------------- docs/Tags.rst | 2 + docs/builtins/alias.rst | 29 ++++ docs/builtins/clear.rst | 4 + docs/builtins/cls.rst | 5 + docs/builtins/devel/dump-rpc.rst | 8 + docs/builtins/die.rst | 4 + docs/builtins/dir.rst | 4 + docs/builtins/disable.rst | 20 +++ docs/builtins/enable.rst | 20 +++ docs/builtins/fpause.rst | 5 + docs/builtins/help.rst | 19 +++ docs/builtins/hide.rst | 8 + docs/builtins/keybinding.rst | 56 +++++++ docs/builtins/kill-lua.rst | 6 + docs/builtins/load.rst | 13 ++ docs/builtins/ls.rst | 34 ++++ docs/builtins/plug.rst | 12 ++ docs/builtins/reload.rst | 14 ++ docs/builtins/sc-script.rst | 9 + docs/builtins/script.rst | 19 +++ docs/builtins/show.rst | 9 + docs/builtins/tags.rst | 9 + docs/builtins/type.rst | 10 ++ docs/builtins/unload.rst | 11 ++ docs/index-tools.rst | 15 +- index.rst | 20 ++- library/lua/helpdb.lua | 74 ++++---- 29 files changed, 390 insertions(+), 332 deletions(-) delete mode 100644 docs/Builtin.rst create mode 100644 docs/builtins/alias.rst create mode 100644 docs/builtins/clear.rst create mode 100644 docs/builtins/cls.rst create mode 100644 docs/builtins/devel/dump-rpc.rst create mode 100644 docs/builtins/die.rst create mode 100644 docs/builtins/dir.rst create mode 100644 docs/builtins/disable.rst create mode 100644 docs/builtins/enable.rst create mode 100644 docs/builtins/fpause.rst create mode 100644 docs/builtins/help.rst create mode 100644 docs/builtins/hide.rst create mode 100644 docs/builtins/keybinding.rst create mode 100644 docs/builtins/kill-lua.rst create mode 100644 docs/builtins/load.rst create mode 100644 docs/builtins/ls.rst create mode 100644 docs/builtins/plug.rst create mode 100644 docs/builtins/reload.rst create mode 100644 docs/builtins/sc-script.rst create mode 100644 docs/builtins/script.rst create mode 100644 docs/builtins/show.rst create mode 100644 docs/builtins/tags.rst create mode 100644 docs/builtins/type.rst create mode 100644 docs/builtins/unload.rst diff --git a/conf.py b/conf.py index 831833e26..7cf4a3a32 100644 --- a/conf.py +++ b/conf.py @@ -105,6 +105,8 @@ def doc_all_dirs(): # TODO: as we scan the docs, parse out the tags and short descriptions and # build a map for use in generating the tags pages and links in the tool # doc footers + for root, _, files in os.walk('docs/builtins'): + tools.extend(doc_dir(root, files, os.path.relpath(root, 'docs/builtins'))) for root, _, files in os.walk('docs/plugins'): tools.extend(doc_dir(root, files, os.path.relpath(root, 'docs/plugins'))) for root, _, files in os.walk('scripts/docs'): @@ -266,6 +268,7 @@ exclude_patterns = [ 'depends/*', 'docs/html/*', 'docs/text/*', + 'docs/builtins/*', 'docs/plugins/*', 'scripts/docs/*', ] diff --git a/docs/Builtin.rst b/docs/Builtin.rst deleted file mode 100644 index 8e59840b4..000000000 --- a/docs/Builtin.rst +++ /dev/null @@ -1,280 +0,0 @@ -.. _built-in-commands: - -Built-in Commands -================= -The following commands are provided by the 'core' components of DFHack, rather -than plugins or scripts. - -.. contents:: - :local: - -.. _alias: - -alias ------ -The ``alias`` command allows configuring aliases to other DFHack commands. -Aliases are resolved immediately after built-in commands, which means that an -alias cannot override a built-in command, but can override a command implemented -by a plugin or script. - -Usage: - -:``alias list``: lists all configured aliases -:``alias add [arguments...]``: adds an alias -:``alias replace [arguments...]``: replaces an existing - alias with a new command, or adds the alias if it does not already exist -:``alias delete ``: removes the specified alias - -Aliases can be given additional arguments when created and invoked, which will -be passed to the underlying command in order. An example with -`devel/print-args`:: - - [DFHack]# alias add pargs devel/print-args example - [DFHack]# pargs text - example - text - - -.. _cls: - -cls ---- -Clear the terminal. Does not delete command history. - - -.. _die: - -die ---- -Instantly kills DF without saving. - - -.. _disable: -.. _enable: - -enable ------- -Many plugins and scripts can be in a distinct enabled or disabled state. Some of -them activate and deactivate automatically depending on the contents of the -world raws. Others store their state in world data. However a number of them -have to be enabled globally, and the init file is the right place to do it. - -Most such plugins or scripts support the built-in ``enable`` and ``disable`` -commands. Calling them at any time without arguments prints a list of enabled -and disabled plugins, and shows whether that can be changed through the same -commands. Passing plugin names to these commands will enable or disable the -specified plugins. For example, to enable the `manipulator` plugin:: - - enable manipulator - -It is also possible to enable or disable multiple plugins at once:: - - enable manipulator search - - -.. _fpause: - -fpause ------- -Forces DF to pause. This is useful when your FPS drops below 1 and you lose -control of the game. - - -.. _help: - -help ----- -Most commands support using the ``help `` built-in command to retrieve -further help without having to look online. ``? `` and ``man `` are -aliases. - -Some commands (including many scripts) instead take ``help`` or ``?`` as an -option on their command line - ie `` help``. - - -.. _hide: - -hide ----- -Hides the DFHack terminal window. Only available on Windows. - - -.. _keybinding: - -keybinding ----------- -To set keybindings, use the built-in ``keybinding`` command. Like any other -command it can be used at any time from the console, but bindings are not -remembered between runs of the game unless re-created in `dfhack.init`. - -Currently, any combinations of Ctrl/Alt/Shift with A-Z, 0-9, F1-F12, or ``\``` -are supported. - -Possible ways to call the command: - -``keybinding list `` - List bindings active for the key combination. -``keybinding clear ...`` - Remove bindings for the specified keys. -``keybinding add "cmdline" "cmdline"...`` - Add bindings for the specified key. -``keybinding set "cmdline" "cmdline"...`` - Clear, and then add bindings for the specified key. - -The ```` parameter above has the following *case-sensitive* syntax:: - - [Ctrl-][Alt-][Shift-]KEY[@context[|context...]] - -where the *KEY* part can be any recognized key and [] denote optional parts. - -When multiple commands are bound to the same key combination, DFHack selects -the first applicable one. Later ``add`` commands, and earlier entries within one -``add`` command have priority. Commands that are not specifically intended for -use as a hotkey are always considered applicable. - -The ``context`` part in the key specifier above can be used to explicitly -restrict the UI state where the binding would be applicable. If called without -parameters, the ``keybinding`` command among other things prints the current -context string. - -Only bindings with a ``context`` tag that either matches the current context -fully, or is a prefix ending at a ``/`` boundary would be considered for -execution, i.e. when in context ``foo/bar/baz``, keybindings restricted to any -of ``@foo/bar/baz``, ``@foo/bar``, ``@foo`` or none will be active. - -Multiple contexts can be specified by separating them with a pipe (``|``) - for -example, ``@foo|bar|baz/foo`` would match anything under ``@foo``, ``@bar``, or -``@baz/foo``. - -Interactive commands like `liquids` cannot be used as hotkeys. - - -.. _kill-lua: - -kill-lua --------- -Stops any currently-running Lua scripts. By default, scripts can only be -interrupted every 256 instructions. Use ``kill-lua force`` to interrupt the next -instruction. - - -.. _load: -.. _unload: -.. _reload: - -load ----- -``load``, ``unload``, and ``reload`` control whether a plugin is loaded into -memory - note that plugins are loaded but disabled unless you explicitly enable -them. Usage:: - - load|unload|reload PLUGIN|(-a|--all) - -Allows dealing with plugins individually by name, or all at once. - -Note that plugins do not maintain their enabled state if they are reloaded, so -you may need to use `enable` to re-enable a plugin after reloading it. - - -.. _ls: -.. _dir: - -ls --- -``ls`` (or ``dir``) does not list files like the Unix command, but rather -available commands. In order to group related commands, each command is -associated with a list of tags. You can filter the listed commands by a -tag or a substring of the command name. Usage: - -:``ls``: Lists all available commands and the tags associated with them - (if any). -:``ls TAG``: Shows only commands that have the given tag. Use the `tags` command - to see the list of available tags. -:``ls STRING``: Shows commands that include the given string. E.g. ``ls auto`` - will show all the commands with "auto" in their names. If the string is also - the name of a tag, then it will be interpreted as a tag name. - -You can also pass some optional parameters to change how ``ls`` behaves: - -:``--notags``: Don't print out the tags associated with each command. -:``--dev``: Include commands intended for developers and modders. - - -.. _plug: - -plug ----- -Lists available plugins and whether they are enabled. - -``plug`` - Lists available plugins (*not* commands implemented by plugins) -``plug [PLUGIN] [PLUGIN] ...`` - List state and detailed description of the given plugins, - including commands implemented by the plugin. - - -.. _sc-script: - -sc-script ---------- -Allows additional scripts to be run when certain events occur (similar to -onLoad\*.init scripts) - - -.. _script: - -script ------- -Reads a text file, and runs each line as a DFHack command as if it had been -typed in by the user - treating the input like `an init file `. - -Some other tools, such as `autobutcher` and `workflow`, export their settings as -the commands to create them - which can later be reloaded with ``script``. - - -.. _show: - -show ----- -Shows the terminal window after it has been `hidden `. Only available on -Windows. You'll need to use it from a `keybinding` set beforehand, or the -in-game `command-prompt`. - - -.. _tags: - -tags ----- - -List the strings that the DFHack tools can be tagged with. You can find groups -of related tools by passing the tag name to `ls`. - -.. _type: - -type ----- -``type command`` shows where ``command`` is implemented. - -.. _devel/dump-rpc: - -devel/dump-rpc --------------- - -Writes RPC endpoint information to the specified file. - -Usage:: - - devel/dump-rpc FILENAME - -Other Commands --------------- -The following commands are *not* built-in, but offer similarly useful functions. - -* `command-prompt` -* `hotkeys` -* `lua` -* `multicmd` -* `nopause` -* `quicksave` -* `rb` -* `repeat` diff --git a/docs/Tags.rst b/docs/Tags.rst index effc0ad23..7a5288cbf 100644 --- a/docs/Tags.rst +++ b/docs/Tags.rst @@ -1,3 +1,5 @@ +.. _tags: + Tags ==== diff --git a/docs/builtins/alias.rst b/docs/builtins/alias.rst new file mode 100644 index 000000000..8ca1f014c --- /dev/null +++ b/docs/builtins/alias.rst @@ -0,0 +1,29 @@ +alias +----- + +Configure helper aliases for other DFHack commands. Aliases are resolved +immediately after built-in commands, which means that an alias cannot override +a built-in command, but can override a command implemented by a plugin or +script. + +Usage: + +- ``alias list`` + lists all configured aliases +- ``alias add [arguments...]`` + adds an alias +- ``alias replace [arguments...]`` + replaces an existing alias with a new command, or adds the alias if it does + not already exist +- ``alias delete `` + removes the specified alias + +Aliases can be given additional arguments when created and invoked, which will +be passed to the underlying command in order. + +Example:: + + [DFHack]# alias add pargs devel/print-args example + [DFHack]# pargs text + example + text diff --git a/docs/builtins/clear.rst b/docs/builtins/clear.rst new file mode 100644 index 000000000..b964a8b3f --- /dev/null +++ b/docs/builtins/clear.rst @@ -0,0 +1,4 @@ +clear +----- + +Clear the terminal screen. This command is an alias for `cls`. diff --git a/docs/builtins/cls.rst b/docs/builtins/cls.rst new file mode 100644 index 000000000..b05354d5a --- /dev/null +++ b/docs/builtins/cls.rst @@ -0,0 +1,5 @@ +cls +--- + +Clear the terminal screen. Can also be invoked as ``clear``. Note that this +command does not delete command history. It just clears the text on the screen. diff --git a/docs/builtins/devel/dump-rpc.rst b/docs/builtins/devel/dump-rpc.rst new file mode 100644 index 000000000..af173a179 --- /dev/null +++ b/docs/builtins/devel/dump-rpc.rst @@ -0,0 +1,8 @@ +devel/dump-rpc +-------------- + +Writes RPC endpoint information to the specified file. + +Usage:: + + devel/dump-rpc diff --git a/docs/builtins/die.rst b/docs/builtins/die.rst new file mode 100644 index 000000000..d2dae484f --- /dev/null +++ b/docs/builtins/die.rst @@ -0,0 +1,4 @@ +die +--- + +Instantly exits DF without saving. diff --git a/docs/builtins/dir.rst b/docs/builtins/dir.rst new file mode 100644 index 000000000..5c2059099 --- /dev/null +++ b/docs/builtins/dir.rst @@ -0,0 +1,4 @@ +dir +--- + +List available DFHack commands. This is an alias of the `ls` command. diff --git a/docs/builtins/disable.rst b/docs/builtins/disable.rst new file mode 100644 index 000000000..11077c883 --- /dev/null +++ b/docs/builtins/disable.rst @@ -0,0 +1,20 @@ +disable +------- + +Deactivate a DFHack tool that has some persistent effect. Many plugins and +scripts can be in a distinct enabled or disabled state. Some of them activate +and deactivate automatically depending on the contents of the world raws. Others +store their state in world data. However a number of them have to be enabled +globally, and the init file is the right place to do it. + +Most such plugins or scripts support the built-in ``enable`` and ``disable`` +commands. Calling them at any time without arguments prints a list of enabled +and disabled plugins, and shows whether that can be changed through the same +commands. Passing plugin names to these commands will enable or disable the +specified plugins. For example, to disable the `manipulator` plugin:: + + disable manipulator + +It is also possible to enable or disable multiple plugins at once:: + + disable manipulator search diff --git a/docs/builtins/enable.rst b/docs/builtins/enable.rst new file mode 100644 index 000000000..eef76c45d --- /dev/null +++ b/docs/builtins/enable.rst @@ -0,0 +1,20 @@ +enable +------ + +Activate a DFHack tool that has some persistent effect. Many plugins and scripts +can be in a distinct enabled or disabled state. Some of them activate and +deactivate automatically depending on the contents of the world raws. Others +store their state in world data. However a number of them have to be enabled +globally, and the init file is the right place to do it. + +Most such plugins or scripts support the built-in ``enable`` and ``disable`` +commands. Calling them at any time without arguments prints a list of enabled +and disabled plugins, and shows whether that can be changed through the same +commands. Passing plugin names to these commands will enable or disable the +specified plugins. For example, to enable the `manipulator` plugin:: + + enable manipulator + +It is also possible to enable or disable multiple plugins at once:: + + enable manipulator search diff --git a/docs/builtins/fpause.rst b/docs/builtins/fpause.rst new file mode 100644 index 000000000..68e4eb07e --- /dev/null +++ b/docs/builtins/fpause.rst @@ -0,0 +1,5 @@ +fpause +------ + +Forces DF to pause. This is useful when your FPS drops below 1 and you lose +control of the game. diff --git a/docs/builtins/help.rst b/docs/builtins/help.rst new file mode 100644 index 000000000..aa9e4b170 --- /dev/null +++ b/docs/builtins/help.rst @@ -0,0 +1,19 @@ +help +---- + +Display help about a command or plugin. + +Usage:: + + help|?|man + help|?|man + +Examples:: + + help blueprint + man blueprint + +Both examples above will display the help text for the `blueprint` command. + +Some commands also take ``help`` or ``?`` as an option on their command line +for the same effect - e.g. ``blueprint help``. diff --git a/docs/builtins/hide.rst b/docs/builtins/hide.rst new file mode 100644 index 000000000..5c8d08452 --- /dev/null +++ b/docs/builtins/hide.rst @@ -0,0 +1,8 @@ +hide +---- + +Hides the DFHack terminal window. You can show it again with the `show` +command, though you'll need to use it from a `keybinding` set beforehand or the +in-game `command-prompt`. + +Only available on Windows. diff --git a/docs/builtins/keybinding.rst b/docs/builtins/keybinding.rst new file mode 100644 index 000000000..bce450e46 --- /dev/null +++ b/docs/builtins/keybinding.rst @@ -0,0 +1,56 @@ +keybinding +---------- + +Create hotkeys that will run DFHack commands. Like any other command it can be +used at any time from the console, but bindings are not remembered between runs +of the game unless re-created in `dfhack.init`. + +Hotkeys can be any combinations of Ctrl/Alt/Shift with A-Z, 0-9, F1-F12, or +``\```. + +Usage: + +- ``keybinding`` + Show some useful information, including the current game context. +- ``keybinding list `` + List bindings active for the key combination. +- ``keybinding clear [...]`` + Remove bindings for the specified keys. +- ``keybinding add "cmdline" ["cmdline"...]`` + Add bindings for the specified key. +- ``keybinding set "cmdline" ["cmdline"...]`` + Clear, and then add bindings for the specified key. + +The ```` parameter above has the following **case-sensitive** syntax:: + + [Ctrl-][Alt-][Shift-]KEY[@context[|context...]] + +where the ``KEY`` part can be any recognized key and [] denote optional parts. + +When multiple commands are bound to the same key combination, DFHack selects +the first applicable one. Later ``add`` commands, and earlier entries within one +``add`` command have priority. Commands that are not specifically intended for +use as a hotkey are always considered applicable. + +The ``context`` part in the key specifier above can be used to explicitly +restrict the UI state where the binding would be applicable. + +Only bindings with a ``context`` tag that either matches the current context +fully, or is a prefix ending at a ``/`` boundary would be considered for +execution, i.e. when in context ``foo/bar/baz``, keybindings restricted to any +of ``@foo/bar/baz``, ``@foo/bar``, ``@foo``, or none will be active. + +Multiple contexts can be specified by separating them with a pipe (``|``) - for +example, ``@foo|bar|baz/foo`` would match anything under ``@foo``, ``@bar``, or +``@baz/foo``. + +Interactive commands like `liquids` cannot be used as hotkeys. + +Examples: + +- ``keybinding add Alt-F1 hotkeys`` + Bind Alt-F1 to run the `hotkeys` command on any screen at any time. +- ``keybinding add Alt-F@dwarfmode gui/quickfort`` + Bind Alt-F to run `gui/quickfort`, but only when on a screen that shows the + main map. + diff --git a/docs/builtins/kill-lua.rst b/docs/builtins/kill-lua.rst new file mode 100644 index 000000000..ee354846a --- /dev/null +++ b/docs/builtins/kill-lua.rst @@ -0,0 +1,6 @@ +kill-lua +-------- + +Gracefully stops any currently-running Lua scripts. Use this command to stop +a misbehaving script that appears to be stuck. Use ``kill-lua force`` if just +``kill-lua`` doesn't work. diff --git a/docs/builtins/load.rst b/docs/builtins/load.rst new file mode 100644 index 000000000..fb654b8a4 --- /dev/null +++ b/docs/builtins/load.rst @@ -0,0 +1,13 @@ +load +---- + +Load and register a plugin library. Also see `unload` and `reload` for related +actions. + +Usage:: + + load [ ...] + load -a|--all + +You can load individual named plugins or all plugins at once. Note that plugins +are disabled after loading/reloading until you explicitly `enable` them. diff --git a/docs/builtins/ls.rst b/docs/builtins/ls.rst new file mode 100644 index 000000000..5eaa9dc01 --- /dev/null +++ b/docs/builtins/ls.rst @@ -0,0 +1,34 @@ +ls +-- + +List available DFHack commands. In order to group related commands, each command +is associated with a list of tags. You can filter the listed commands by a tag +or a substring of the command name. The `dir` command is an alias of this +command. + +Usage: + +- ``ls []`` + Lists all available commands and the tags associated with them. +- ``ls []`` + Shows only commands that have the given tag. Use the `tags` command to see + the list of available tags. +- ``ls []`` + Shows commands that include the given string. E.g. ``ls quick`` will show all + the commands with "quick" in their names. If the string is also the name of a + tag, then it will be interpreted as a tag name. + +You can also pass some optional parameters to change how ``ls`` behaves: + +- ``--notags`` + Don't print out the tags associated with each command. +- ``--dev`` + Include commands intended for developers and modders. + +Examples: + +- ``ls adventure`` + Lists all commands with the ``adventure`` tag. +- ``ls --dev trigger`` + Lists all commands, including developer and modding commands, that match the + substring "trigger" diff --git a/docs/builtins/plug.rst b/docs/builtins/plug.rst new file mode 100644 index 000000000..da0adb657 --- /dev/null +++ b/docs/builtins/plug.rst @@ -0,0 +1,12 @@ +plug +---- + +Lists available plugins and whether they are enabled. + +Usage: + +- ``plug`` + Lists available plugins and whether they are enabled. +- ``plug [ ...]`` + Shows the commands implemented by the named plugins and whether the plugins + are enabled. diff --git a/docs/builtins/reload.rst b/docs/builtins/reload.rst new file mode 100644 index 000000000..8f28e5ca6 --- /dev/null +++ b/docs/builtins/reload.rst @@ -0,0 +1,14 @@ +reload +------ + +Reload a loaded plugin. Developer use this command to reload a plugin that they +are actively modifying. Also see `load` and `unload` for related actions. + +Usage:: + + reload [ ...] + reload -a|--all + +You can reload individual named plugins or all plugins at once. Note that +plugins are disabled after loading/reloading until you explicitly `enable` +them. diff --git a/docs/builtins/sc-script.rst b/docs/builtins/sc-script.rst new file mode 100644 index 000000000..1d5d177c7 --- /dev/null +++ b/docs/builtins/sc-script.rst @@ -0,0 +1,9 @@ +sc-script +--------- + +Runs commands when game state changes occur. This is similar to the static +`init-files` but can be set dynamically. + +Usage:: + + sc-script diff --git a/docs/builtins/script.rst b/docs/builtins/script.rst new file mode 100644 index 000000000..ae2b9f0f4 --- /dev/null +++ b/docs/builtins/script.rst @@ -0,0 +1,19 @@ +script +------ + +Executes a batch file of DFHack commands. It reads a text file and runs each +line as a DFHack command as if it had been typed in by the user - treating the +input like `an init file `. + +Some other tools, such as `autobutcher` and `workflow`, export their settings as +the commands to create them - which can later be reloaded with ``script``. + +Usage:: + + script + +Examples: + +- ``script startup.txt`` + Executes the commands in ``startup.txt``, which exists in your DF game + directory. diff --git a/docs/builtins/show.rst b/docs/builtins/show.rst new file mode 100644 index 000000000..8a6f9b446 --- /dev/null +++ b/docs/builtins/show.rst @@ -0,0 +1,9 @@ +show +---- + +Unhides the DFHack terminal window. Useful if you have hidden the terminal with +`hide` and you want it back. Since the terminal window won't be available to run +this command, you'll need to use it from a `keybinding` set beforehand or the +in-game `command-prompt`. + +Only available on Windows. diff --git a/docs/builtins/tags.rst b/docs/builtins/tags.rst new file mode 100644 index 000000000..9de0f8df3 --- /dev/null +++ b/docs/builtins/tags.rst @@ -0,0 +1,9 @@ +tags +---- + +List the strings that DFHack tools can be tagged with. You can find groups of +related tools by passing the tag name to the `ls` command. + +Usage:: + + tags diff --git a/docs/builtins/type.rst b/docs/builtins/type.rst new file mode 100644 index 000000000..c4c752bef --- /dev/null +++ b/docs/builtins/type.rst @@ -0,0 +1,10 @@ +type +---- + +Describes how a command is implemented. DFHack commands can be provided by +plugins, scripts, or by the core library itself. The ``type`` command can tell +you which is the source of a particular command. + +Usage:: + + type diff --git a/docs/builtins/unload.rst b/docs/builtins/unload.rst new file mode 100644 index 000000000..f4069ed88 --- /dev/null +++ b/docs/builtins/unload.rst @@ -0,0 +1,11 @@ +unload +------ + +Unload a plugin from memory. Also see `load` and `reload` for related actions. + +Usage:: + + unload [ ...] + unload -a|--all + +You can unload individual named plugins or all plugins at once. diff --git a/docs/index-tools.rst b/docs/index-tools.rst index dca14ae3b..a9c540d66 100644 --- a/docs/index-tools.rst +++ b/docs/index-tools.rst @@ -1,24 +1,15 @@ .. _tools-index: -============ -DFHack Tools -============ +================== +DFHack Tools Index +================== These pages contain information about the plugins, scripts, and built-in commands distributed with DFHack. -.. note:: - In order to avoid user confusion, as a matter of policy all GUI tools - display the word :guilabel:`DFHack` on the screen somewhere while active. - - When that is not appropriate because they merely add keybinding hints to - existing DF screens, they deliberately use red instead of green for the key. - .. toctree:: :titlesonly: :glob: - /docs/Tags - /docs/Builtin /docs/tools/* /docs/tools/*/* diff --git a/index.rst b/index.rst index 29ac3c129..3e5cdec5d 100644 --- a/index.rst +++ b/index.rst @@ -30,7 +30,25 @@ User Manual /docs/Installing /docs/Support /docs/Core - /docs/index-tools /docs/guides/index /docs/index-about /docs/index-dev + +Tools +===== + +DFHack commands, plugins, and scripts are grouped by tags to make it easier to +find groups of related tools. + +.. note:: + In order to avoid user confusion, as a matter of policy all GUI tools + display the word :guilabel:`DFHack` on the screen somewhere while active. + + When that is not appropriate because they merely add keybinding hints to + existing DF screens, they deliberately use red instead of green for the key. + +.. toctree:: + :maxdepth: 1 + + /docs/Tags + /docs/index-tools diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index b52f91f22..18ba5b746 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -16,7 +16,6 @@ local _ENV = mkmodule('helpdb') -- paths local RENDERED_PATH = 'hack/docs/docs/tools/' -local BUILTIN_HELP = 'hack/docs/docs/Builtin.txt' local TAG_DEFINITIONS = 'hack/docs/docs/Tags.txt' -- used when reading help text embedded in script sources @@ -25,6 +24,7 @@ local SCRIPT_DOC_END = ']====]' local SCRIPT_DOC_BEGIN_RUBY = '=begin' local SCRIPT_DOC_END_RUBY = '=end' +-- enums local ENTRY_TYPES = { BUILTIN='builtin', PLUGIN='plugin', @@ -38,6 +38,33 @@ local HELP_SOURCES = { SCRIPT='script', } +-- builtins +local BUILTINS = { + 'alias', + 'clear', + 'cls', + 'devel/dump-rpc', + 'die', + 'dir', + 'disable', + 'enable', + 'fpause', + 'help', + 'hide', + 'keybinding', + 'kill-lua', + 'load', + 'ls', + 'plug', + 'reload', + 'script', + 'sc-script', + 'show', + 'tags', + 'type', + 'unload', +} + -- entry name -> { -- entry_types (set of ENTRY_TYPES), -- short_help (string), @@ -246,46 +273,15 @@ local function update_db(old_db, db, source, entry_name, kwargs) end end -local BUILTINS = { - alias='Configure helper aliases for other DFHack commands.', - cls='Clear the console screen.', - clear='Clear the console screen.', - ['devel/dump-rpc']='Write RPC endpoint information to a file.', - die='Force DF to close immediately, without saving.', - enable='Enable a plugin or persistent script.', - disable='Disable a plugin or persistent script.', - fpause='Force DF to pause.', - help='Usage help for the given plugin, command, or script.', - hide='Hide the terminal window (Windows only).', - keybinding='Modify bindings of commands to in-game key shortcuts.', - ['kill-lua']='Stop a misbehaving Lua script.', - ['load']='Load and register a plugin library.', - unload='Unregister and unload a plugin.', - reload='Unload and reload a plugin library.', - ls='List commands, optionally filtered by a tag or substring.', - dir='List commands, optionally filtered by a tag or substring.', - plug='List plugins and whether they are enabled.', - ['sc-script']='Automatically run specified scripts on state change events.', - script='Run commands specified in a file.', - show='Show a hidden terminal window (Windows only).', - tags='List the tags that the DFHack tools are grouped by.', - ['type']='Discover how a command is implemented.', -} - -- add the builtin commands to the db local function scan_builtins(old_db, db) - local entry = make_default_entry('builtin', - {[ENTRY_TYPES.BUILTIN]=true, [ENTRY_TYPES.COMMAND]=true}, - HELP_SOURCES.RENDERED, 0, BUILTIN_HELP) - -- read in builtin help - local f = io.open(BUILTIN_HELP) - if f then - entry.long_help = f:read('*all') - end - for b,short_help in pairs(BUILTINS) do - local builtin_entry = copyall(entry) - builtin_entry.short_help = short_help - db[b] = builtin_entry + local entry_types = {[ENTRY_TYPES.BUILTIN]=true, [ENTRY_TYPES.COMMAND]=true} + for _,builtin in ipairs(BUILTINS) do + update_db(old_db, db, + has_rendered_help(builtin) and + HELP_SOURCES.RENDERED or HELP_SOURCES.STUB, + builtin, + {entry_types=entry_types}) end end From 019856883bce4c911ee01f2383b48c838ecdb002 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 11:10:56 -0700 Subject: [PATCH 223/854] update sc-script docs based on code spelunking --- docs/builtins/sc-script.rst | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/builtins/sc-script.rst b/docs/builtins/sc-script.rst index 1d5d177c7..4c99f3225 100644 --- a/docs/builtins/sc-script.rst +++ b/docs/builtins/sc-script.rst @@ -2,8 +2,19 @@ sc-script --------- Runs commands when game state changes occur. This is similar to the static -`init-files` but can be set dynamically. +`init-files` but is slightly more flexible since it can be set dynamically. -Usage:: +Usage: - sc-script +- ``sc-script [help]`` + Show the list of valid event names. +- ``sc-script list []`` + List the currently registered files for all events or the specified event. +- ``sc-script add|remove [ ...]`` + Register or unregister a file to be run for the specified event. + +Examples: + +- ``sc-script add SC_MAP_LOADED spawn_extra_monsters.init`` + Registers the ``spawn_extra_monsters.init`` file to be run whenever a new map + is loaded. From d27def7128f598a933edbbdf5342e44486414632 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 11:16:05 -0700 Subject: [PATCH 224/854] spacing is important otherwise the usage and examples lists don't put the explanations on the next line --- docs/builtins/alias.rst | 10 +++++----- docs/builtins/disable.rst | 4 ++-- docs/builtins/enable.rst | 4 ++-- docs/builtins/keybinding.rst | 17 ++++++++--------- docs/builtins/ls.rst | 22 +++++++++++----------- docs/builtins/plug.rst | 6 +++--- docs/builtins/sc-script.rst | 10 +++++----- docs/builtins/script.rst | 4 ++-- 8 files changed, 38 insertions(+), 39 deletions(-) diff --git a/docs/builtins/alias.rst b/docs/builtins/alias.rst index 8ca1f014c..034d0f33a 100644 --- a/docs/builtins/alias.rst +++ b/docs/builtins/alias.rst @@ -9,14 +9,14 @@ script. Usage: - ``alias list`` - lists all configured aliases + Lists all configured aliases - ``alias add [arguments...]`` - adds an alias + Adds an alias - ``alias replace [arguments...]`` - replaces an existing alias with a new command, or adds the alias if it does - not already exist + Replaces an existing alias with a new command, or adds the alias if it does + not already exist - ``alias delete `` - removes the specified alias + Removes the specified alias Aliases can be given additional arguments when created and invoked, which will be passed to the underlying command in order. diff --git a/docs/builtins/disable.rst b/docs/builtins/disable.rst index 11077c883..d871ff06e 100644 --- a/docs/builtins/disable.rst +++ b/docs/builtins/disable.rst @@ -13,8 +13,8 @@ and disabled plugins, and shows whether that can be changed through the same commands. Passing plugin names to these commands will enable or disable the specified plugins. For example, to disable the `manipulator` plugin:: - disable manipulator + disable manipulator It is also possible to enable or disable multiple plugins at once:: - disable manipulator search + disable manipulator search diff --git a/docs/builtins/enable.rst b/docs/builtins/enable.rst index eef76c45d..8338cc7f4 100644 --- a/docs/builtins/enable.rst +++ b/docs/builtins/enable.rst @@ -13,8 +13,8 @@ and disabled plugins, and shows whether that can be changed through the same commands. Passing plugin names to these commands will enable or disable the specified plugins. For example, to enable the `manipulator` plugin:: - enable manipulator + enable manipulator It is also possible to enable or disable multiple plugins at once:: - enable manipulator search + enable manipulator search diff --git a/docs/builtins/keybinding.rst b/docs/builtins/keybinding.rst index bce450e46..7ab92c206 100644 --- a/docs/builtins/keybinding.rst +++ b/docs/builtins/keybinding.rst @@ -11,15 +11,15 @@ Hotkeys can be any combinations of Ctrl/Alt/Shift with A-Z, 0-9, F1-F12, or Usage: - ``keybinding`` - Show some useful information, including the current game context. + Show some useful information, including the current game context. - ``keybinding list `` - List bindings active for the key combination. + List bindings active for the key combination. - ``keybinding clear [...]`` - Remove bindings for the specified keys. + Remove bindings for the specified keys. - ``keybinding add "cmdline" ["cmdline"...]`` - Add bindings for the specified key. + Add bindings for the specified key. - ``keybinding set "cmdline" ["cmdline"...]`` - Clear, and then add bindings for the specified key. + Clear, and then add bindings for the specified key. The ```` parameter above has the following **case-sensitive** syntax:: @@ -49,8 +49,7 @@ Interactive commands like `liquids` cannot be used as hotkeys. Examples: - ``keybinding add Alt-F1 hotkeys`` - Bind Alt-F1 to run the `hotkeys` command on any screen at any time. + Bind Alt-F1 to run the `hotkeys` command on any screen at any time. - ``keybinding add Alt-F@dwarfmode gui/quickfort`` - Bind Alt-F to run `gui/quickfort`, but only when on a screen that shows the - main map. - + Bind Alt-F to run `gui/quickfort`, but only when on a screen that shows the + main map. diff --git a/docs/builtins/ls.rst b/docs/builtins/ls.rst index 5eaa9dc01..fd2e532ed 100644 --- a/docs/builtins/ls.rst +++ b/docs/builtins/ls.rst @@ -9,26 +9,26 @@ command. Usage: - ``ls []`` - Lists all available commands and the tags associated with them. + Lists all available commands and the tags associated with them. - ``ls []`` - Shows only commands that have the given tag. Use the `tags` command to see - the list of available tags. + Shows only commands that have the given tag. Use the `tags` command to see + the list of available tags. - ``ls []`` - Shows commands that include the given string. E.g. ``ls quick`` will show all - the commands with "quick" in their names. If the string is also the name of a - tag, then it will be interpreted as a tag name. + Shows commands that include the given string. E.g. ``ls quick`` will show + all the commands with "quick" in their names. If the string is also the + name of a tag, then it will be interpreted as a tag name. You can also pass some optional parameters to change how ``ls`` behaves: - ``--notags`` - Don't print out the tags associated with each command. + Don't print out the tags associated with each command. - ``--dev`` - Include commands intended for developers and modders. + Include commands intended for developers and modders. Examples: - ``ls adventure`` - Lists all commands with the ``adventure`` tag. + Lists all commands with the ``adventure`` tag. - ``ls --dev trigger`` - Lists all commands, including developer and modding commands, that match the - substring "trigger" + Lists all commands, including developer and modding commands, that match the + substring "trigger" diff --git a/docs/builtins/plug.rst b/docs/builtins/plug.rst index da0adb657..93bd452a7 100644 --- a/docs/builtins/plug.rst +++ b/docs/builtins/plug.rst @@ -6,7 +6,7 @@ Lists available plugins and whether they are enabled. Usage: - ``plug`` - Lists available plugins and whether they are enabled. + Lists available plugins and whether they are enabled. - ``plug [ ...]`` - Shows the commands implemented by the named plugins and whether the plugins - are enabled. + Shows the commands implemented by the named plugins and whether the plugins + are enabled. diff --git a/docs/builtins/sc-script.rst b/docs/builtins/sc-script.rst index 4c99f3225..20a0e9497 100644 --- a/docs/builtins/sc-script.rst +++ b/docs/builtins/sc-script.rst @@ -7,14 +7,14 @@ Runs commands when game state changes occur. This is similar to the static Usage: - ``sc-script [help]`` - Show the list of valid event names. + Show the list of valid event names. - ``sc-script list []`` - List the currently registered files for all events or the specified event. + List the currently registered files for all events or the specified event. - ``sc-script add|remove [ ...]`` - Register or unregister a file to be run for the specified event. + Register or unregister a file to be run for the specified event. Examples: - ``sc-script add SC_MAP_LOADED spawn_extra_monsters.init`` - Registers the ``spawn_extra_monsters.init`` file to be run whenever a new map - is loaded. + Registers the ``spawn_extra_monsters.init`` file to be run whenever a new + map is loaded. diff --git a/docs/builtins/script.rst b/docs/builtins/script.rst index ae2b9f0f4..ae4017ca7 100644 --- a/docs/builtins/script.rst +++ b/docs/builtins/script.rst @@ -15,5 +15,5 @@ Usage:: Examples: - ``script startup.txt`` - Executes the commands in ``startup.txt``, which exists in your DF game - directory. + Executes the commands in ``startup.txt``, which exists in your DF game + directory. From 2207f2699216a4a7d974ea423c1bb1d2bc5a9ea3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 11:34:58 -0700 Subject: [PATCH 225/854] update docs for 3dveins --- docs/plugins/3dveins.rst | 33 +++++++++++++++++++++++---------- plugins/3dveins.cpp | 9 +++------ 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 0e6841792..9a074a4c8 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -1,14 +1,27 @@ 3dveins ======= -Removes all existing veins from the map and generates new ones using -3D Perlin noise, in order to produce a layout that smoothly flows between -Z levels. The vein distribution is based on the world seed, so running -the command for the second time should produce no change. It is best to -run it just once immediately after embark. - -This command is intended as only a cosmetic change, so it takes -care to exactly preserve the mineral counts reported by `prospect` ``all``. -The amounts of different layer stones may slightly change in some cases -if vein mass shifts between Z layers. + +Rewrites layer veins to expand in 3D space. Existing, flat veins are removed +and new 3D veins that naturally span z-levels are generated in their place. +The transformation preserves the mineral counts reported by `prospect`. + +Usage:: + + 3dveins [verbose] + +The ``verbose`` option prints out extra information to the console. + +Example:: + + 3dveins + +New veins are generated using 3D Perlin noise in order to produce a layout that +flows smoothly between z-levels. The vein distribution is based on the world +seed, so running the command for the second time should produce no change. It is +best to run it just once immediately after embark. + +This command is intended as only a cosmetic change, so it takes care to exactly +preserve the mineral counts reported by ``prospect all``. The amounts of layer +stones may slightly change in some cases if vein mass shifts between z layers. The only undo option is to restore your save from backup. diff --git a/plugins/3dveins.cpp b/plugins/3dveins.cpp index 929ee24f6..eaf741caf 100644 --- a/plugins/3dveins.cpp +++ b/plugins/3dveins.cpp @@ -52,12 +52,9 @@ command_result cmd_3dveins(color_ostream &out, std::vector & param DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "3dveins", "Rewrites the veins to make them extend in 3D space.", - cmd_3dveins, false, - " Run this after embark to change all veins on the map to a shape\n" - " that consistently spans Z levels. The operation preserves the\n" - " mineral counts reported by prospect.\n" - )); + "3dveins", + "Rewrites the veins to make them extend in 3D space.", + cmd_3dveins)); return CR_OK; } From 02ba204f5b94eeca18e3c42a5ce4466516ebc02a Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 11:39:52 -0700 Subject: [PATCH 226/854] update docs for add-spatter --- docs/plugins/add-spatter.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/plugins/add-spatter.rst b/docs/plugins/add-spatter.rst index 15b99ff52..b6bb97cf7 100644 --- a/docs/plugins/add-spatter.rst +++ b/docs/plugins/add-spatter.rst @@ -1,6 +1,9 @@ add-spatter =========== -This plugin makes reactions with names starting with ``SPATTER_ADD_`` -produce contaminants on the items instead of improvements. The plugin is -intended to give some use to all those poisons that can be bought from caravans, -so they're immune to being washed away by water or destroyed by `clean`. + +Make tagged reactions produce contaminants. The plugin is intended to give some +use to all those poisons that can be bought from caravans. It automatically +enables itself when you load a world with reactions that include names starting +with ``SPATTER_ADD_``. These reactions will then produce contaminants on items +instead of improvements. The contaminants are immune to being washed away by +water or destroyed by `clean`. From 5ff31e0cc13c5725e5158939b6fcf36b9ecd0a6e Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 12:36:11 -0700 Subject: [PATCH 227/854] remove defunct advtools plugin --- docs/plugins/adv-bodyswap.rst | 12 - plugins/CMakeLists.txt | 1 - plugins/advtools.cpp | 827 ---------------------------------- 3 files changed, 840 deletions(-) delete mode 100644 docs/plugins/adv-bodyswap.rst delete mode 100644 plugins/advtools.cpp diff --git a/docs/plugins/adv-bodyswap.rst b/docs/plugins/adv-bodyswap.rst deleted file mode 100644 index 46b961517..000000000 --- a/docs/plugins/adv-bodyswap.rst +++ /dev/null @@ -1,12 +0,0 @@ -adv-bodyswap -============ -This allows taking control over your followers and other creatures in adventure -mode. For example, you can make them pick up new arms and armor and equip them -properly. - -Usage: - -* When viewing unit details, body-swaps into that unit. -* In the main adventure mode screen, reverts transient swap. - -:dfhack-keybind:`adv-bodyswap` diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index af646759f..b9d63839e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -82,7 +82,6 @@ if(BUILD_SUPPORTED) dfhack_plugin(3dveins 3dveins.cpp) dfhack_plugin(add-spatter add-spatter.cpp) - # dfhack_plugin(advtools advtools.cpp) dfhack_plugin(autochop autochop.cpp) dfhack_plugin(autoclothing autoclothing.cpp) dfhack_plugin(autodump autodump.cpp) diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp deleted file mode 100644 index a1965cba0..000000000 --- a/plugins/advtools.cpp +++ /dev/null @@ -1,827 +0,0 @@ -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include "MiscUtils.h" -#include "modules/World.h" -#include "modules/Translation.h" -#include "modules/Materials.h" -#include "modules/Maps.h" -#include "modules/Items.h" -#include "modules/Gui.h" -#include "modules/Units.h" - -#include "DataDefs.h" -#include "df/world.h" -#include "df/ui_advmode.h" -#include "df/item.h" -#include "df/unit.h" -#include "df/unit_inventory_item.h" -#include "df/unit_relationship_type.h" -#include "df/map_block.h" -#include "df/nemesis_record.h" -#include "df/historical_figure.h" -#include "df/general_ref_is_nemesisst.h" -#include "df/general_ref_contains_itemst.h" -#include "df/general_ref_contained_in_itemst.h" -#include "df/general_ref_unit_holderst.h" -#include "df/general_ref_building_civzone_assignedst.h" -#include "df/material.h" -#include "df/craft_material_class.h" -#include "df/viewscreen_optionst.h" -#include "df/viewscreen_dungeonmodest.h" -#include "df/viewscreen_dungeon_monsterstatusst.h" -#include "df/nemesis_flags.h" - -#include - -using namespace DFHack; -using namespace df::enums; - -using df::nemesis_record; -using df::historical_figure; - -using namespace DFHack::Translation; -/* -advtools -======== -A package of different adventure mode tools. Usage: - -:list-equipped [all]: List armor and weapons equipped by your companions. - If all is specified, also lists non-metal clothing. -:metal-detector [all-types] [non-trader]: - Reveal metal armor and weapons in shops. The options - disable the checks on item type and being in shop. -*/ - -DFHACK_PLUGIN("advtools"); -REQUIRE_GLOBAL(world); -REQUIRE_GLOBAL(ui_advmode); - -/********************* - * PLUGIN INTERFACE * - *********************/ - -static bool bodyswap_hotkey(df::viewscreen *top); - -command_result adv_bodyswap (color_ostream &out, std::vector & parameters); -command_result adv_tools (color_ostream &out, std::vector & parameters); - -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) -{ - if (!ui_advmode) - return CR_OK; - - commands.push_back(PluginCommand( - "advtools", "Adventure mode tools.", - adv_tools, false, - " list-equipped [all]\n" - " List armor and weapons equipped by your companions.\n" - " If all is specified, also lists non-metal clothing.\n" - " metal-detector [all-types] [non-trader]\n" - " Reveal metal armor and weapons in shops. The options\n" - " disable the checks on item type and being in shop.\n" - )); - - commands.push_back(PluginCommand( - "adv-bodyswap", "Change the adventurer unit.", - adv_bodyswap, bodyswap_hotkey, - " - When viewing unit details, body-swaps into that unit.\n" - " - In the main adventure mode screen, reverts transient swap.\n" - "Options:\n" - " force\n" - " Allow swapping into non-companion units.\n" - " permanent\n" - " Permanently change the unit to be the adventurer.\n" - " Otherwise it will revert if adv-bodyswap is called\n" - " in the main screen, or if the main menu, Fast Travel\n" - " or Sleep/Wait screen is opened.\n" - " noinherit\n" - " In permanent mode, don't reassign companions to the new unit.\n" - )); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -df::nemesis_record *getPlayerNemesis(color_ostream &out, bool restore_swap); - -DFHACK_PLUGIN_IS_ENABLED(in_transient_swap); - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) { - case SC_WORLD_LOADED: - case SC_WORLD_UNLOADED: - in_transient_swap = false; - break; - default: - break; - } - return CR_OK; -} - -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) -{ - // Revert transient swaps before trouble happens - if (in_transient_swap) - { - auto screen = Core::getTopViewscreen(); - bool revert = false; - - if (strict_virtual_cast(screen)) - { - using namespace df::enums::ui_advmode_menu; - - switch (ui_advmode->menu) - { - case Travel: - // was also Sleep, now equivalent - revert = true; - break; - default: - break; - } - } - else if (strict_virtual_cast(screen)) - { - // Options may mean save game - revert = true; - } - - if (revert) - { - getPlayerNemesis(out, true); - in_transient_swap = false; - } - } - - return CR_OK; -} - -/********************* - * UTILITY FUNCTIONS * - *********************/ - -static bool bodyswap_hotkey(df::viewscreen *top) -{ - return !!virtual_cast(top) || - !!virtual_cast(top); -} - -bool bodySwap(color_ostream &out, df::unit *player) -{ - if (!player) - { - out.printerr("Unit to swap is NULL\n"); - return false; - } - - auto &vec = world->units.active; - - int idx = linear_index(vec, player); - if (idx < 0) - { - out.printerr("Unit to swap not found: %d\n", player->id); - return false; - } - - if (idx != 0) - std::swap(vec[0], vec[idx]); - - return true; -} - -df::nemesis_record *getPlayerNemesis(color_ostream &out, bool restore_swap) -{ - auto real_nemesis = vector_get(world->nemesis.all, ui_advmode->player_id); - if (!real_nemesis || !real_nemesis->unit) - { - out.printerr("Invalid player nemesis id: %d\n", ui_advmode->player_id); - return NULL; - } - - if (restore_swap) - { - df::unit *ctl = world->units.active[0]; - auto ctl_nemesis = Units::getNemesis(ctl); - - if (ctl_nemesis != real_nemesis) - { - if (!bodySwap(out, real_nemesis->unit)) - return NULL; - - auto name = TranslateName(&real_nemesis->unit->name, false); - out.print("Returned into the body of %s.\n", name.c_str()); - } - - real_nemesis->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = -1; - in_transient_swap = false; - } - - return real_nemesis; -} - -void changeGroupLeader(df::nemesis_record *new_nemesis, df::nemesis_record *old_nemesis) -{ - auto &cvec = new_nemesis->companions; - - // Swap companions - cvec.swap(old_nemesis->companions); - - vector_erase_at(cvec, linear_index(cvec, new_nemesis->id)); - insert_into_vector(cvec, old_nemesis->id); - - // Update follow - new_nemesis->group_leader_id = -1; - new_nemesis->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = -1; - - for (unsigned i = 0; i < cvec.size(); i++) - { - auto nm = df::nemesis_record::find(cvec[i]); - if (!nm) - continue; - - nm->group_leader_id = new_nemesis->id; - if (nm->unit) - nm->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = new_nemesis->unit_id; - } -} - -void copyAcquaintances(df::nemesis_record *new_nemesis, df::nemesis_record *old_nemesis) -{ - auto &svec = old_nemesis->unit->adventurer_knows; - auto &tvec = new_nemesis->unit->adventurer_knows; - - for (unsigned i = 0; i < svec.size(); i++) - insert_into_vector(tvec, svec[i]); - - insert_into_vector(tvec, old_nemesis->unit_id); -} - -void sortCompanionNemesis(std::vector *list, int player_id = -1) -{ - std::map table; - std::vector output; - - output.reserve(list->size()); - - if (player_id < 0) - { - auto real_nemesis = vector_get(world->nemesis.all, ui_advmode->player_id); - if (real_nemesis) - player_id = real_nemesis->id; - } - - // Index records; find the player - for (size_t i = 0; i < list->size(); i++) - { - auto item = (*list)[i]; - if (item->id == player_id) - output.push_back(item); - else - table[item->figure->id] = item; - } - - // Pull out the items by the persistent sort order - auto &order_vec = ui_advmode->companions.all_histfigs; - for (size_t i = 0; i < order_vec.size(); i++) - { - auto it = table.find(order_vec[i]); - if (it == table.end()) - continue; - output.push_back(it->second); - table.erase(it); - } - - // The remaining ones in reverse id order - for (auto it = table.rbegin(); it != table.rend(); ++it) - output.push_back(it->second); - - list->swap(output); -} - -void listCompanions(color_ostream &out, std::vector *list, bool units = true) -{ - nemesis_record *player = getPlayerNemesis(out, false); - if (!player) - return; - - list->push_back(player); - - for (size_t i = 0; i < player->companions.size(); i++) - { - auto item = nemesis_record::find(player->companions[i]); - if (item && (item->unit || !units)) - list->push_back(item); - } -} - -std::string getUnitNameProfession(df::unit *unit) -{ - std::string name = TranslateName(&unit->name, false) + ", "; - if (unit->custom_profession.empty()) - name += ENUM_ATTR_STR(profession, caption, unit->profession); - else - name += unit->custom_profession; - return name; -} - -enum InventoryMode { - INV_HAULED, - INV_WEAPON, - INV_WORN, - INV_IN_CONTAINER -}; - -typedef std::pair inv_item; - -static void listContainerInventory(std::vector *list, df::item *container) -{ - auto &refs = container->general_refs; - for (size_t i = 0; i < refs.size(); i++) - { - auto ref = refs[i]; - if (!strict_virtual_cast(ref)) - continue; - - df::item *child = ref->getItem(); - if (!child) continue; - - list->push_back(inv_item(child, INV_IN_CONTAINER)); - listContainerInventory(list, child); - } -} - -void listUnitInventory(std::vector *list, df::unit *unit) -{ - auto &items = unit->inventory; - for (size_t i = 0; i < items.size(); i++) - { - auto item = items[i]; - InventoryMode mode; - - switch (item->mode) { - case df::unit_inventory_item::Hauled: - mode = INV_HAULED; - break; - case df::unit_inventory_item::Weapon: - mode = INV_WEAPON; - break; - default: - mode = INV_WORN; - } - - list->push_back(inv_item(item->item, mode)); - listContainerInventory(list, item->item); - } -} - -bool isShopItem(df::item *item) -{ - for (size_t k = 0; k < item->general_refs.size(); k++) - { - auto ref = item->general_refs[k]; - if (virtual_cast(ref)) - return true; - } - - return false; -} - -bool isWeaponArmor(df::item *item) -{ - using namespace df::enums::item_type; - - switch (item->getType()) { - case HELM: - case ARMOR: - case WEAPON: - case AMMO: - case GLOVES: - case PANTS: - case SHOES: - return true; - default: - return false; - } -} - -int containsMetalItems(df::item *item, bool all, bool non_trader, bool rec = false) -{ - int cnt = 0; - - auto &refs = item->general_refs; - for (size_t i = 0; i < refs.size(); i++) - { - auto ref = refs[i]; - - if (strict_virtual_cast(ref)) - return 0; - if (!rec && strict_virtual_cast(ref)) - return 0; - - if (strict_virtual_cast(ref)) - { - df::item *child = ref->getItem(); - if (!child) continue; - - cnt += containsMetalItems(child, all, non_trader, true); - } - } - - if (!non_trader && !isShopItem(item)) - return cnt; - if (!all && !isWeaponArmor(item)) - return cnt; - - MaterialInfo minfo(item); - if (minfo.getCraftClass() != craft_material_class::Metal) - return cnt; - - return ++cnt; -} - -void joinCounts(std::map &counts) -{ - for (auto it = counts.begin(); it != counts.end(); it++) - { - df::coord pt = it->first; - while (pt.x > 0 && counts.count(pt - df::coord(1,0,0))) - pt.x--; - while (pt.y > 0 &&counts.count(pt - df::coord(0,1,0))) - pt.y--; - while (pt.x < 0 && counts.count(pt + df::coord(1,0,0))) - pt.x++; - while (pt.y < 0 &&counts.count(pt + df::coord(0,1,0))) - pt.y++; - - if (pt == it->first) - continue; - - counts[pt] += it->second; - it->second = 0; - } -} - -/********************* - * FORMATTING * - *********************/ - -static void printCompanionHeader(color_ostream &out, size_t i, df::unit *unit) -{ - out.color(COLOR_GREY); - - if (i < 28) - out << char('a'+i); - else - out << i; - - out << ": " << getUnitNameProfession(unit); - if (Units::isDead(unit)) - out << " (DEAD)"; - if (Units::isGhost(unit)) - out << " (GHOST)"; - out << endl; - - out.reset_color(); -} - -static size_t formatSize(std::vector *out, const std::map in, size_t *cnt) -{ - size_t len = 0; - - for (auto it = in.begin(); it != in.end(); ++it) - { - std::string line = it->first; - if (it->second != 1) - line += stl_sprintf(" [%d]", it->second); - len = std::max(len, line.size()); - out->push_back(line); - } - - if (out->empty()) - { - out->push_back("(none)"); - len = 6; - } - - if (cnt) - *cnt = std::max(*cnt, out->size()); - - return len; -} - -static std::string formatDirection(df::coord delta) -{ - std::string ns, ew, dir; - - if (delta.x > 0) - ew = "E"; - else if (delta.x < 0) - ew = "W"; - - if (delta.y > 0) - ns = "S"; - else if (delta.y < 0) - ns = "N"; - - if (abs(delta.x) > abs(delta.y)*5) - dir = ew; - else if (abs(delta.y) > abs(delta.x)*5) - dir = ns; - else if (abs(delta.x) > abs(delta.y)*2) - dir = ew + ns + ew; - else if (abs(delta.y) > abs(delta.x)*2) - dir = ns + ns + ew; - else if (delta.x || delta.y) - dir = ns + ew; - else - dir = "***"; - - int dist = (int)sqrt((double)(delta.x*delta.x + delta.y*delta.y)); - return stl_sprintf("%d away %s %+d", dist, dir.c_str(), delta.z); -} - -static void printEquipped(color_ostream &out, df::unit *unit, bool all) -{ - std::vector items; - listUnitInventory(&items, unit); - - std::map head, body, legs, weapons; - - for (auto it = items.begin(); it != items.end(); ++it) - { - df::item *item = it->first; - - // Skip non-worn non-weapons - ItemTypeInfo iinfo(item); - - bool is_weapon = (it->second == INV_WEAPON || iinfo.type == item_type::AMMO); - if (!(is_weapon || it->second == INV_WORN)) - continue; - - // Skip non-metal, unless all - MaterialInfo minfo(item); - df::craft_material_class mclass = minfo.getCraftClass(); - - bool is_metal = (mclass == craft_material_class::Metal); - if (!(is_weapon || all || is_metal)) - continue; - - // Format the name - std::string name; - if (is_metal) - name = minfo.toString() + " "; - else if (mclass != craft_material_class::None) - name = toLower(ENUM_KEY_STR(craft_material_class,mclass)) + " "; - name += iinfo.toString(); - - // Add to the right table - int count = item->getStackSize(); - - if (is_weapon) - { - weapons[name] += count; - continue; - } - - switch (iinfo.type) { - case item_type::HELM: - head[name] += count; - break; - case item_type::ARMOR: - case item_type::GLOVES: - case item_type::BACKPACK: - case item_type::QUIVER: - body[name] += count; - break; - case item_type::PANTS: - case item_type::SHOES: - legs[name] += count; - break; - default: - weapons[name] += count; - } - } - - std::vector cols[4]; - size_t sizes[4]; - size_t lines = 0; - - sizes[0] = formatSize(&cols[0], head, &lines); - sizes[1] = formatSize(&cols[1], body, &lines); - sizes[2] = formatSize(&cols[2], legs, &lines); - sizes[3] = formatSize(&cols[3], weapons, &lines); - - for (size_t i = 0; i < lines; i++) - { - for (int j = 0; j < 4; j++) - { - size_t sz = std::max(sizes[j], size_t(18)); - out << "| " << std::left << std::setw(sz) << vector_get(cols[j],i) << " "; - } - - out << "|" << std::endl; - } -} - -/********************* - * COMMANDS * - *********************/ - -command_result adv_bodyswap (color_ostream &out, std::vector & parameters) -{ - // HOTKEY COMMAND; CORE IS SUSPENDED - bool force = false; - bool permanent = false; - bool no_make_leader = false; - - for (unsigned i = 0; i < parameters.size(); i++) - { - auto &item = parameters[i]; - - if (item == "force") - force = true; - else if (item == "permanent") - permanent = true; - else if (item == "noinherit") - no_make_leader = true; - else - return CR_WRONG_USAGE; - } - - // Get the real player; undo previous transient swap - auto real_nemesis = getPlayerNemesis(out, true); - if (!real_nemesis) - return CR_FAILURE; - - // Get the unit to swap to - auto new_unit = Gui::getSelectedUnit(out, true); - auto new_nemesis = Units::getNemesis(new_unit); - - if (!new_nemesis) - { - if (new_unit) - { - out.printerr("Cannot swap into a non-historical unit.\n"); - return CR_FAILURE; - } - - return CR_OK; - } - - if (new_nemesis == real_nemesis) - return CR_OK; - - // Verify it's a companion - if (!force && linear_index(real_nemesis->companions, new_nemesis->id) < 0) - { - out.printerr("This is not your companion - use force to bodyswap.\n"); - return CR_FAILURE; - } - - // Swap - if (!bodySwap(out, new_nemesis->unit)) - return CR_FAILURE; - - auto name = TranslateName(&new_nemesis->unit->name, false); - out.print("Swapped into the body of %s.\n", name.c_str()); - - // Permanently re-link everything - if (permanent) - { - using namespace df::enums::nemesis_flags; - - ui_advmode->player_id = linear_index(world->nemesis.all, new_nemesis); - - // Flag 0 appears to be the 'active adventurer' flag, and - // the player_id field above seems to be computed using it - // when a savegame is loaded. - // Also, unless this is set, it is impossible to retire. - real_nemesis->flags.set(ACTIVE_ADVENTURER, false); - new_nemesis->flags.set(ACTIVE_ADVENTURER, true); - - real_nemesis->flags.set(RETIRED_ADVENTURER, true); // former retired adventurer - new_nemesis->flags.set(ADVENTURER, true); // blue color in legends - - // Reassign companions and acquaintances - if (!no_make_leader) - { - changeGroupLeader(new_nemesis, real_nemesis); - copyAcquaintances(new_nemesis, real_nemesis); - } - } - else - { - in_transient_swap = true; - - // Make the player unit follow around to avoid bad consequences - // if it is unloaded before the transient swap is reverted. - real_nemesis->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = new_nemesis->unit_id; - } - - return CR_OK; -} - -command_result adv_tools (color_ostream &out, std::vector & parameters) -{ - if (parameters.empty()) - return CR_WRONG_USAGE; - - CoreSuspender suspend; - - const auto &command = parameters[0]; - if (command == "list-equipped") - { - bool all = false; - for (size_t i = 1; i < parameters.size(); i++) - { - if (parameters[i] == "all") - all = true; - else - return CR_WRONG_USAGE; - } - - std::vector list; - - listCompanions(out, &list); - sortCompanionNemesis(&list); - - for (size_t i = 0; i < list.size(); i++) - { - auto item = list[i]; - auto unit = item->unit; - - printCompanionHeader(out, i, unit); - printEquipped(out, unit, all); - } - - return CR_OK; - } - else if (command == "metal-detector") - { - bool all = false, non_trader = false; - for (size_t i = 1; i < parameters.size(); i++) - { - if (parameters[i] == "all-types") - all = true; - else if (parameters[i] == "non-trader") - non_trader = true; - else - return CR_WRONG_USAGE; - } - - auto *player = getPlayerNemesis(out, false); - if (!player) - return CR_FAILURE; - - df::coord player_pos = player->unit->pos; - - int total = 0; - std::map counts; - - auto &items = world->items.all; - for (size_t i = 0; i < items.size(); i++) - { - df::item *item = items[i]; - - int num = containsMetalItems(item, all, non_trader); - if (!num) - continue; - - df::map_block *block = Maps::getTileBlock(item->pos); - if (!block) - continue; - - total += num; - counts[(item->pos - player_pos)/10] += num; - - auto &designations = block->designation; - auto &dgn = designations[item->pos.x%16][item->pos.y%16]; - - dgn.bits.hidden = 0; // revealed - dgn.bits.pile = 1; // visible - } - - joinCounts(counts); - - out.print("%d items of metal merchandise found in the vicinity.\n", total); - for (auto it = counts.begin(); it != counts.end(); it++) - { - if (!it->second) - continue; - - df::coord delta = it->first * 10; - out.print(" %s: %d\n", formatDirection(delta).c_str(), it->second); - } - - return CR_OK; - } - else - return CR_WRONG_USAGE; -} From 51c817191ca186a38a237457b895569bc10e7d9d Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 12:52:38 -0700 Subject: [PATCH 228/854] update docs for autochop --- docs/plugins/autochop.rst | 22 +++++++++++++--------- plugins/autochop.cpp | 7 +++---- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/plugins/autochop.rst b/docs/plugins/autochop.rst index 4a669480f..7c517e901 100644 --- a/docs/plugins/autochop.rst +++ b/docs/plugins/autochop.rst @@ -1,16 +1,20 @@ autochop ======== -Automatically manage tree cutting designation to keep available logs withing given -quotas. -Open the dashboard by running:: +Auto-harvest trees when low on stockpiled logs. This plugin can designate trees +for chopping when your stocks are low on logs. + +Usage:: enable autochop -The plugin must be activated (with :kbd:`d`-:kbd:`t`-:kbd:`c`-:kbd:`a`) before -it can be used. You can then set logging quotas and restrict designations to -specific burrows (with 'Enter') if desired. The plugin's activity cycle runs -once every in game day. +Then, open the settings menu with :kbd:`c` from the designations menu (the +option appears when you have "Chop Down Trees" selected with :kbd:`d`-:kbd:`t`). + +Set your desired thresholds and enable autochopping with :kbd:`a`. + +You can also restrict autochopping to specific burrows. Highlight a burrow name +with the Up/Down arrow keys and hit :kbd:`Enter` to mark it as the autochop +burrrow. -If you add ``enable autochop`` to your dfhack.init there will be a hotkey to -open the dashboard from the chop designation menu. +Autochop checks your stock of logs and designates trees once every in game day. diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 04ae8af71..d96e7a972 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -935,10 +935,9 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( - "autochop", "Auto-harvest trees when low on stockpiled logs", - df_autochop, false, - "Opens the automated chopping control screen. Specify 'debug' to forcibly save settings.\n" - )); + "autochop", + "Auto-harvest trees when low on stockpiled logs.", + df_autochop)); initialize(); return CR_OK; From 3969a366ecfa45f11923dcbb594923be87c25fc0 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 13:02:03 -0700 Subject: [PATCH 229/854] update docs for autoclothing --- docs/plugins/autoclothing.rst | 13 +++++++++++-- plugins/autoclothing.cpp | 15 +++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/plugins/autoclothing.rst b/docs/plugins/autoclothing.rst index 7169844dd..a3c53a65a 100644 --- a/docs/plugins/autoclothing.rst +++ b/docs/plugins/autoclothing.rst @@ -1,11 +1,20 @@ autoclothing ============ -Automatically manage clothing work orders, allowing the user to set how many of -each clothing type every citizen should have. Usage:: +Automatically manage clothing work orders. It allows you to set how many of each +clothing type every citizen should have. +Usage:: + + enable autoclothing autoclothing [number] +``material`` can be "cloth", "silk", "yarn", or "leather". The ``item`` can be +anything your civilization can produce, such as "dress" or "mitten". + +When invoked without a number, it shows the current configuration for that +material and item. + Examples: * ``autoclothing cloth "short skirt" 10``: diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index d19e647d5..83eb0398a 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -1,5 +1,3 @@ - -// some headers required for a plugin. Nothing special, just the basics. #include "Core.h" #include #include @@ -184,16 +182,9 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector [number]\n" - "Example:\n" - " autoclothing cloth \"short skirt\" 10\n" - " Sets the desired number of cloth short skirts available per citizen to 10.\n" - " autoclothing cloth dress\n" - " Displays the currently set number of cloth dresses chosen per citizen.\n" - )); + "autoclothing", + "Automatically manage clothing work orders", + autoclothing)); return CR_OK; } From 25bc59297bdc48e83bf39d4fbb9ac7b3c284ea8d Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 13:28:16 -0700 Subject: [PATCH 230/854] update docs for autodump (and related hotkeys) --- docs/plugins/autodump-destroy-here.rst | 10 ++++ docs/plugins/autodump-destroy-item.rst | 13 +++++ docs/plugins/autodump.rst | 81 +++++++++++++++++++++----- plugins/autodump.cpp | 34 ++++------- 4 files changed, 101 insertions(+), 37 deletions(-) create mode 100644 docs/plugins/autodump-destroy-here.rst create mode 100644 docs/plugins/autodump-destroy-item.rst diff --git a/docs/plugins/autodump-destroy-here.rst b/docs/plugins/autodump-destroy-here.rst new file mode 100644 index 000000000..375a799c0 --- /dev/null +++ b/docs/plugins/autodump-destroy-here.rst @@ -0,0 +1,10 @@ +autodump-destroy-here +===================== + +Destroy items marked for dumping under cursor. If called again before the game +is resumed, cancels destruction of the items. This is an alias for the +`autodump` command ``autodump destroy-here``, intended for use as a keybinding. + +Usage:: + + autodump-destroy-here diff --git a/docs/plugins/autodump-destroy-item.rst b/docs/plugins/autodump-destroy-item.rst new file mode 100644 index 000000000..a9f3554c3 --- /dev/null +++ b/docs/plugins/autodump-destroy-item.rst @@ -0,0 +1,13 @@ +autodump-destroy-item +===================== + +Destroy the selected item. The item may be selected in the :kbd:`k` list or in +the container item list. If called again before the game is resumed, cancels +destruction of the item. + +This command is intended for use as a keybinding. See the `autodump` command +for other dumping/destroying options. + +Usage:: + + autodump-destroy-item diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst index 9bd74cff7..8064583ea 100644 --- a/docs/plugins/autodump.rst +++ b/docs/plugins/autodump.rst @@ -1,29 +1,82 @@ autodump ======== -This plugin adds an option to the :kbd:`q` menu for stckpiles when `enabled `. -When autodump is enabled for a stockpile, any items placed in the stockpile will + +Quickly designate or teleport items to be dumped. When `enabled `, this +plugin adds an option to the :kbd:`q` menu for stockpiles. When the ``autodump`` +option is selected for the stockpile, any items placed in the stockpile will automatically be designated to be dumped. -Alternatively, you can use it to quickly move all items designated to be dumped. -Items are instantly moved to the cursor position, the dump flag is unset, -and the forbid flag is set, as if it had been dumped normally. -Be aware that any active dump item tasks still point at the item. +When invoked as a command, it can instantly move all items designated to be +dumped to the tile under the cursor. After moving the items, the dump flag is +unset and the forbid flag is set, just as if it had been dumped normally. Be +aware that dwarves that are en route to pick up the item for dumping may still +come and move the item to your dump zone. + +The cursor must be placed on a floor tile so the items can be dumped there. + +Usage:: -Cursor must be placed on a floor tile so the items can be dumped there. + enable autodump + autodump [] Options: -:destroy: Destroy instead of dumping. Doesn't require a cursor. - If called again before the game is resumed, cancels destroy. -:destroy-here: As ``destroy``, but only the selected item in the :kbd:`k` list, - or inside a container. +- ``destroy`` + Destroy instead of dumping. Doesn't require a cursor. If ``autodump`` is + called again with this option before the game is resumed, it cancels + the destroy action. +- ``destroy-here`` + As ``destroy``, but only the selected item in the :kbd:`k` list, or inside a + container. +- ``visible`` + Only process items that are not hidden. +- ``hidden`` + Only process hidden items. +- ``forbidden`` + Only process forbidden items (default: only unforbidden). + +Examples: + +- ``autodump`` + Teleports all unforbidden items marked for dumping to the cursor position. +- ``autodump destroy`` + Destroys all unforbidden items marked for dumping + + Alias ``autodump-destroy-here``, for keybindings. :dfhack-keybind:`autodump-destroy-here` -:visible: Only process items that are not hidden. -:hidden: Only process hidden items. -:forbidden: Only process forbidden items (default: only unforbidden). ``autodump-destroy-item`` destroys the selected item, which may be selected in the :kbd:`k` list, or inside a container. If called again before the game is resumed, cancels destruction of the item. :dfhack-keybind:`autodump-destroy-item` + + + + commands.push_back(PluginCommand( + "autodump", "Teleport items marked for dumping to the cursor.", + df_autodump, false, + " This utility lets you quickly move all items designated to be dumped.\n" + " Items are instantly moved to the cursor position, the dump flag is unset,\n" + " and the forbid flag is set, as if it had been dumped normally.\n" + " Be aware that any active dump item tasks still point at the item.\n" + "Options:\n" + " destroy - instead of dumping, destroy the items instantly.\n" + " destroy-here - only affect the tile under cursor.\n" + " visible - only process items that are not hidden.\n" + " hidden - only process hidden items.\n" + " forbidden - only process forbidden items (default: only unforbidden).\n" + )); + commands.push_back(PluginCommand( + "autodump-destroy-here", "Destroy items marked for dumping under cursor.", + df_autodump_destroy_here, Gui::cursor_hotkey, + " Identical to autodump destroy-here, but intended for use as keybinding.\n" + )); + commands.push_back(PluginCommand( + "autodump-destroy-item", "Destroy the selected item.", + df_autodump_destroy_item, Gui::any_item_hotkey, + " Destroy the selected item. The item may be selected\n" + " in the 'k' list, or inside a container. If called\n" + " again before the game is resumed, cancels destroy.\n" + )); + return CR_OK; diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index 054d28f77..fb215c26b 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -275,31 +275,19 @@ command_result df_autodump_destroy_item(color_ostream &out, vector & pa DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( - "autodump", "Teleport items marked for dumping to the cursor.", - df_autodump, false, - " This utility lets you quickly move all items designated to be dumped.\n" - " Items are instantly moved to the cursor position, the dump flag is unset,\n" - " and the forbid flag is set, as if it had been dumped normally.\n" - " Be aware that any active dump item tasks still point at the item.\n" - "Options:\n" - " destroy - instead of dumping, destroy the items instantly.\n" - " destroy-here - only affect the tile under cursor.\n" - " visible - only process items that are not hidden.\n" - " hidden - only process hidden items.\n" - " forbidden - only process forbidden items (default: only unforbidden).\n" - )); + "autodump", + "Teleport items marked for dumping to the cursor.", + df_autodump)); commands.push_back(PluginCommand( - "autodump-destroy-here", "Destroy items marked for dumping under cursor.", - df_autodump_destroy_here, Gui::cursor_hotkey, - " Identical to autodump destroy-here, but intended for use as keybinding.\n" - )); + "autodump-destroy-here", + "Destroy items marked for dumping under cursor.", + df_autodump_destroy_here, + Gui::cursor_hotkey)); commands.push_back(PluginCommand( - "autodump-destroy-item", "Destroy the selected item.", - df_autodump_destroy_item, Gui::any_item_hotkey, - " Destroy the selected item. The item may be selected\n" - " in the 'k' list, or inside a container. If called\n" - " again before the game is resumed, cancels destroy.\n" - )); + "autodump-destroy-item", + "Destroy the selected item.", + df_autodump_destroy_item, + Gui::any_item_hotkey)); return CR_OK; } From 3ca7997d3e87b45858098419a7ea50eefb337c8f Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 13:49:51 -0700 Subject: [PATCH 231/854] update docs for autofarm --- docs/plugins/autofarm.rst | 41 +++++++++++++++++++++++++-------------- plugins/autofarm.cpp | 18 ++++------------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/docs/plugins/autofarm.rst b/docs/plugins/autofarm.rst index b330517d0..d5bb12e41 100644 --- a/docs/plugins/autofarm.rst +++ b/docs/plugins/autofarm.rst @@ -1,21 +1,32 @@ autofarm ======== -Automatically handles crop selection in farm plots based on current plant -stocks, and selects crops for planting if current stock is below a threshold. -Selected crops are dispatched on all farmplots. (Note that this plugin replaces -an older Ruby script of the same name.) - -Use the `enable` or `disable ` commands to change whether this plugin is -enabled. +Automatically manage farm crop selection. This plugin periodically scans your +plant stocks and assigns crops to your farm plots based on which plant stocks +are low (as long as you have the appropriate seeds). The target threshold for +each crop type is configurable. Usage: -* ``autofarm runonce``: - Updates all farm plots once, without enabling the plugin -* ``autofarm status``: - Prints status information, including any applied limits -* ``autofarm default 30``: - Sets the default threshold -* ``autofarm threshold 150 helmet_plump tail_pig``: - Sets thresholds of individual plants +- ``enable autofarm`` + Enable the plugin and start managing crop assignment. +* ``autofarm runonce`` + Updates all farm plots once, without enabling the plugin. +* ``autofarm status`` + Prints status information, including any defined thresholds. +* ``autofarm default `` + Sets the default threshold. +* ``autofarm threshold [ ...]`` + Sets thresholds of individual plant types. + +You can find the identifiers for the crop types in your world by running the +following command:: + + lua "for _,plant in ipairs(df.global.world.raws.plants.all) do if plant.flags.SEED then print(plant.id) end end" + +Examples: + +- ``autofarm default 30`` + Set the default threshold to 30. +- ``autofarm threshold 150 MUSHROOM_HELMET_PLUMP GRASS_TAIL_PIG`` + Set the threshold for Plump Helmets and Pig Tails to 150 diff --git a/plugins/autofarm.cpp b/plugins/autofarm.cpp index 2dafdee49..1a02b6a06 100644 --- a/plugins/autofarm.cpp +++ b/plugins/autofarm.cpp @@ -38,15 +38,6 @@ DFHACK_PLUGIN("autofarm"); DFHACK_PLUGIN_IS_ENABLED(enabled); -const char* tagline = "Automatically handle crop selection in farm plots based on current plant stocks."; -const char* usage = ( - "``enable autofarm``: Enables the plugin\n" - "``autofarm runonce``: Updates farm plots (one-time only)\n" - "``autofarm status``: Prints status information\n" - "``autofarm default 30``: Sets the default threshold\n" - "``autofarm threshold 150 helmet_plump tail_pig``: Sets thresholds\n" - ); - class AutoFarm { private: std::map thresholds; @@ -330,7 +321,7 @@ public: void status(color_ostream& out) { - out << (enabled ? "Running." : "Stopped.") << '\n'; + out << "Autofarm is " << (enabled ? "Active." : "Stopped.") << '\n'; for (auto& lc : lastCounts) { auto plant = world->raws.plants.all[lc.first]; @@ -355,10 +346,9 @@ DFhackCExport command_result plugin_init(color_ostream& out, std::vector ()); return CR_OK; From b1916f16b191e975d56900101c21a2b7d99b2443 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 13:59:11 -0700 Subject: [PATCH 232/854] update docs for autogems --- docs/plugins/autogems-reload.rst | 9 +++++++++ docs/plugins/autogems.rst | 13 +++++++++---- plugins/autogems.cpp | 20 ++------------------ 3 files changed, 20 insertions(+), 22 deletions(-) create mode 100644 docs/plugins/autogems-reload.rst diff --git a/docs/plugins/autogems-reload.rst b/docs/plugins/autogems-reload.rst new file mode 100644 index 000000000..a37117746 --- /dev/null +++ b/docs/plugins/autogems-reload.rst @@ -0,0 +1,9 @@ +autogems-reload +=============== + +Reloads the autogems configuration file. You might need to do this if you have +manually modified the contents while the game is running. + +Usage:: + + autogems-reload diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index 59800e1b9..ad3b0f9c3 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -1,7 +1,12 @@ autogems ======== -Creates a new Workshop Order setting, automatically cutting rough gems -when `enabled `. -See `gui/autogems` for a configuration UI. If necessary, the ``autogems-reload`` -command reloads the configuration file produced by that script. +Automatically cut rough gems. This plugin periodically scans your stocks of +rough gems and creates manager orders for cutting them at a Jeweler's Workshop. + +Usage:: + + enable autogems + +Run `gui/autogems` for a configuration UI, or access the new ``Auto Cut Gems`` +option from the Current Workshop Orders screen (:kbd:`o`-:kbd:`W`). diff --git a/plugins/autogems.cpp b/plugins/autogems.cpp index fcd8fcdc7..d296caf7f 100644 --- a/plugins/autogems.cpp +++ b/plugins/autogems.cpp @@ -41,19 +41,6 @@ typedef int32_t mat_index; typedef std::map gem_map; bool running = false; -const char *tagline = "Creates a new Workshop Order setting, automatically cutting rough gems."; -const char *usage = ( - " enable autogems\n" - " Enable the plugin.\n" - " disable autogems\n" - " Disable the plugin.\n" - "\n" - "While enabled, the Current Workshop Orders screen (o-W) have a new option:\n" - " g: Auto Cut Gems\n" - "\n" - "While this option is enabled, jobs will be created in Jeweler's Workshops\n" - "to cut any accessible rough gems.\n" -); std::set blacklist; void add_task(mat_index gem_type, df::building_workshopst *workshop) { @@ -385,11 +372,8 @@ DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( "autogems-reload", - "Reload autogems config file", - cmd_reload_config, - false, - "Reload autogems config file" - )); + "Reload autogems config file.", + cmd_reload_config)); return CR_OK; } From 6c760d1a3d5d4f92240e1f996aa282213b3d6052 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 14:33:24 -0700 Subject: [PATCH 233/854] update docs for autohauler --- docs/plugins/autohauler.rst | 59 +++++++++++++++++++++++++++---------- plugins/autohauler.cpp | 42 ++------------------------ 2 files changed, 47 insertions(+), 54 deletions(-) diff --git a/docs/plugins/autohauler.rst b/docs/plugins/autohauler.rst index 289874f94..4defac18d 100644 --- a/docs/plugins/autohauler.rst +++ b/docs/plugins/autohauler.rst @@ -1,20 +1,49 @@ autohauler ========== -Autohauler is an autolabor fork. -Rather than the all-of-the-above means of autolabor, autohauler will instead -only manage hauling labors and leave skilled labors entirely to the user, who -will probably use Dwarf Therapist to do so. +Automatically manage hauling labors. Similar to `autolabor`, but instead of +managing all labors, ``autohauler`` only addresses hauling labors, leaving the +assignment of skilled labors entirely up to you. You can use the in-game +`manipulator` UI or an external tool like Dwarf Therapist to do so. -Idle dwarves will be assigned the hauling labors; everyone else (including -those currently hauling) will have the hauling labors removed. This is to -encourage every dwarf to do their assigned skilled labors whenever possible, -but resort to hauling when those jobs are not available. This also implies -that the user will have a very tight skill assignment, with most skilled -labors only being assigned to just one dwarf, no dwarf having more than two -active skilled labors, and almost every non-military dwarf having at least -one skilled labor assigned. +Idle dwarves who are not on active military duty will be assigned the hauling +labors; everyone else (including those currently hauling) will have the hauling +labors removed. This is to encourage every dwarf to do their assigned skilled +labors whenever possible, but resort to hauling when those jobs are not +available. This also implies that the user will have a very tight skill +assignment, with most skilled labors only being assigned to just a few dwarves +and almost every non-military dwarf having at least one skilled labor assigned. -Autohauler allows skills to be flagged as to prevent hauling labors from -being assigned when the skill is present. By default this is the unused -ALCHEMIST labor but can be changed by the user. +Autohauler allows a skill to be used as a flag to exempt a dwarf from +``autohauler``'s effects. By default, this is the unused ALCHEMIST labor, but it +can be changed by the user. + +Usage: + +- ``enable autohauler`` + Start managing hauling labors. +- ``autohauler status`` + Show autohauler status and status of fort dwarves. +- ``autohauler haulers`` + Set whether a particular labor should be assigned to haulers. +- ``autohauler allow|forbid`` + Set whether a particular labor should mark a dwarf as exempt from hauling. + By default, only the ``ALCHEMIST`` labor is set to ``forbid``. +- ``autohauler reset-all| reset`` + Reset a particular labor (or all labors) to their default + haulers/allow/forbid state. +- ``autohauler list`` + Show the active configuration for all labors. +- ``autohauler frameskip `` + Set the number of frames between runs of autohauler. +- ``autohauler debug`` + In the next cycle, output the state of every dwarf. + +Examples: +- ``autohauler HAUL_STONE haulers`` + Set stone hauling as a hauling labor (this is already the default). +- ``autohauler RECOVER_WOUNDED allow`` + Allow the "Recover wounded" labor to be manually assigned by the player. By + default it is automatically given to haulers. +- ``autohauler MINE forbid`` + Don't assign hauling labors to dwarves with the Mining labor enabled. diff --git a/plugins/autohauler.cpp b/plugins/autohauler.cpp index 35bf90ca1..b52e9cfb5 100644 --- a/plugins/autohauler.cpp +++ b/plugins/autohauler.cpp @@ -712,45 +712,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector haulers\n" - " Set a labor to be handled by hauler dwarves.\n" - " autohauler allow\n" - " Allow hauling if a specific labor is enabled.\n" - " autohauler forbid\n" - " Forbid hauling if a specific labor is enabled.\n" - " autohauler reset\n" - " Return a labor to the default handling.\n" - " autohauler reset-all\n" - " Return all labors to the default handling.\n" - " autohauler frameskip \n" - " Set the number of frames between runs of autohauler.\n" - " autohauler list\n" - " List current status of all labors.\n" - " autohauler status\n" - " Show basic status information.\n" - " autohauler debug\n" - " In the next cycle, will output the state of every dwarf.\n" - "Function:\n" - " When enabled, autohauler periodically checks your dwarves and assigns\n" - " hauling jobs to idle dwarves while removing them from busy dwarves.\n" - " This plugin, in contrast to autolabor, is explicitly designed to be\n" - " used alongside Dwarf Therapist.\n" - " Warning: autohauler will override any manual changes you make to\n" - " hauling labors while it is enabled...but why would you make them?\n" - "Examples:\n" - " autohauler HAUL_STONE haulers\n" - " Set stone hauling as a hauling labor.\n" - " autohauler BOWYER allow\n" - " Allow hauling when the bowyer labor is enabled.\n" - " autohauler MINE forbid\n" - " Forbid hauling while the mining labor is disabled." - )); + "autohauler", + "Automatically manage hauling labors.", + autohauler)); // Initialize plugin labor lists init_state(out); From 87e67ea8b3d6b3ee6c26d88660ab1931d91d3a0d Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 14:53:06 -0700 Subject: [PATCH 234/854] update docs for autolabor --- docs/plugins/autohauler.rst | 3 +- docs/plugins/autolabor.rst | 100 ++++++++++++++++++++---------------- plugins/autolabor.cpp | 45 ++-------------- 3 files changed, 61 insertions(+), 87 deletions(-) diff --git a/docs/plugins/autohauler.rst b/docs/plugins/autohauler.rst index 4defac18d..d1743f5ef 100644 --- a/docs/plugins/autohauler.rst +++ b/docs/plugins/autohauler.rst @@ -21,7 +21,8 @@ can be changed by the user. Usage: - ``enable autohauler`` - Start managing hauling labors. + Start managing hauling labors. This is normally all you need to do. + Autohauler works well on default settings. - ``autohauler status`` Show autohauler status and status of fort dwarves. - ``autohauler haulers`` diff --git a/docs/plugins/autolabor.rst b/docs/plugins/autolabor.rst index b63b9102b..b60146d28 100644 --- a/docs/plugins/autolabor.rst +++ b/docs/plugins/autolabor.rst @@ -1,70 +1,82 @@ autolabor ========= -Automatically manage dwarf labors to efficiently complete jobs. -Autolabor tries to keep as many dwarves as possible busy but -also tries to have dwarves specialize in specific skills. -The key is that, for almost all labors, once a dwarf begins a job it will finish that -job even if the associated labor is removed. Autolabor therefore frequently checks -which dwarf or dwarves should take new jobs for that labor, and sets labors accordingly. -Labors with equipment (mining, hunting, and woodcutting), which are abandoned -if labors change mid-job, are handled slightly differently to minimise churn. +Automatically manage dwarf labors. Autolabor attempts to keep as many dwarves as +possible busy while allowing dwarves to specialize in specific skills. + +Autolabor frequently checks how many jobs of each type are available and sets +labors proportionally in order to get them all done quickly. Labors with +equipment -- mining, hunting, and woodcutting -- which are abandoned if labors +change mid-job, are handled slightly differently to minimise churn. + +Dwarves on active military duty or dwarves assigned to burrows are left +untouched by autolabor. .. warning:: - *autolabor will override any manual changes you make to labors while - it is enabled, including through other tools such as Dwarf Therapist* + autolabor will override any manual changes you make to labors while it is + enabled, including through other tools such as Dwarf Therapist. -Simple usage: +Usage:: -:enable autolabor: Enables the plugin with default settings. (Persistent per fortress) -:disable autolabor: Disables the plugin. + enable autolabor -Anything beyond this is optional - autolabor works well on the default settings. +Anything beyond this is optional - autolabor works well with the default +settings. Once you have enabled it in a fortress, it stays enabled until you +explicitly disable it, even if you save and reload your game. -By default, each labor is assigned to between 1 and 200 dwarves (2-200 for mining). -By default 33% of the workforce become haulers, who handle all hauling jobs as well -as cleaning, pulling levers, recovering wounded, removing constructions, and filling ponds. -Other jobs are automatically assigned as described above. Each of these settings can be adjusted. +By default, each labor is assigned to between 1 and 200 dwarves (2-200 for +mining). 33% of the workforce become haulers, who handle all hauling jobs as +well as cleaning, pulling levers, recovering wounded, removing constructions, +and filling ponds. Other jobs are automatically assigned as described above. +Each of these settings can be adjusted. -Jobs are rarely assigned to nobles with responsibilities for meeting diplomats or merchants, -never to the chief medical dwarf, and less often to the bookeeper and manager. +Jobs are rarely assigned to nobles with responsibilities for meeting diplomats +or merchants, never to the chief medical dwarf, and less often to the bookeeper +and manager. -Hunting is never assigned without a butchery, and fishing is never assigned without a fishery. +Hunting is never assigned without a butchery, and fishing is never assigned +without a fishery. -For each labor a preference order is calculated based on skill, biased against masters of other -trades and excluding those who can't do the job. The labor is then added to the best -dwarves for that labor. We assign at least the minimum number of dwarfs, in order of preference, -and then assign additional dwarfs that meet any of these conditions: +For each labor, a preference order is calculated based on skill, biased against +masters of other trades and excluding those who can't do the job. The labor is +then added to the best dwarves for that labor, then to additional +dwarfs that meet any of these conditions: * The dwarf is idle and there are no idle dwarves assigned to this labor * The dwarf has non-zero skill associated with the labor * The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled. -We stop assigning dwarfs when we reach the maximum allowed. +We stop assigning dwarves when we reach the maximum allowed. Advanced usage: -:autolabor []: - Set number of dwarves assigned to a labor. -:autolabor haulers: Set a labor to be handled by hauler dwarves. -:autolabor disable: Turn off autolabor for a specific labor. -:autolabor reset: Return a labor to the default handling. -:autolabor reset-all: Return all labors to the default handling. -:autolabor list: List current status of all labors. -:autolabor status: Show basic status information. +- ``autolabor [] []`` + Set range of dwarves assigned to a labor, optionally specifying the size of + the pool of most skilled dwarves that will ever be considered for this + labor. +- ``autolabor haulers`` + Set a labor to be handled by hauler dwarves. +- ``autolabor disable`` + Turn off autolabor for a specific labor. +- ``autolabor reset-all| reset`` + Return a labor (or all labors) to the default handling. +- ``autolabor list`` + List current status of all labors. +- ``autolabor status`` + Show basic status information. See `autolabor-artisans` for a differently-tuned setup. Examples: -``autolabor MINE`` - Keep at least 5 dwarves with mining enabled. -``autolabor CUT_GEM 1 1`` - Keep exactly 1 dwarf with gemcutting enabled. -``autolabor COOK 1 1 3`` - Keep 1 dwarf with cooking enabled, selected only from the top 3. -``autolabor FEED_WATER_CIVILIANS haulers`` - Have haulers feed and water wounded dwarves. -``autolabor CUTWOOD disable`` - Turn off autolabor for wood cutting. +- ``autolabor MINE 5`` + Keep at least 5 dwarves with mining enabled. +- ``autolabor CUT_GEM 1 1`` + Keep exactly 1 dwarf with gemcutting enabled. +- ``autolabor COOK 1 1 3`` + Keep 1 dwarf with cooking enabled, selected only from the top 3. +- ``autolabor FEED_WATER_CIVILIANS haulers`` + Have haulers feed and water wounded dwarves. +- ``autolabor CUTWOOD disable`` + Turn off autolabor for wood cutting. diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index ba116c1c5..15c6903b4 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -686,48 +686,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector [] []\n" - " Set number of dwarves assigned to a labor.\n" - " autolabor haulers\n" - " Set a labor to be handled by hauler dwarves.\n" - " autolabor disable\n" - " Turn off autolabor for a specific labor.\n" - " autolabor reset\n" - " Return a labor to the default handling.\n" - " autolabor reset-all\n" - " Return all labors to the default handling.\n" - " autolabor list\n" - " List current status of all labors.\n" - " autolabor status\n" - " Show basic status information.\n" - "Function:\n" - " When enabled, autolabor periodically checks your dwarves and enables or\n" - " disables labors. It tries to keep as many dwarves as possible busy but\n" - " also tries to have dwarves specialize in specific skills.\n" - " Warning: autolabor will override any manual changes you make to labors\n" - " while it is enabled.\n" - " To prevent particular dwarves from being managed by autolabor, put them\n" - " in any burrow.\n" - " To restrict the assignment of a labor to only the top most skilled\n" - " dwarves, add a talent pool number .\n" - "Examples:\n" - " autolabor MINE 2\n" - " Keep at least 2 dwarves with mining enabled.\n" - " autolabor CUT_GEM 1 1\n" - " Keep exactly 1 dwarf with gemcutting enabled.\n" - " autolabor COOK 1 1 3\n" - " Keep 1 dwarf with cooking enabled, selected only from the top 3.\n" - " autolabor FEED_WATER_CIVILIANS haulers\n" - " Have haulers feed and water wounded dwarves.\n" - " autolabor CUTWOOD disable\n" - " Turn off autolabor for wood cutting.\n" - )); + "autolabor", + "Automatically manage dwarf labors.", + autolabor)); init_state(); From 0ba3a4684b6cd55cafcb7d773b8000cc4f14bb0f Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 16:13:04 -0700 Subject: [PATCH 235/854] update docs for automaterial --- docs/plugins/automaterial.rst | 54 +++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/docs/plugins/automaterial.rst b/docs/plugins/automaterial.rst index 81de0cb3d..51701a1fc 100644 --- a/docs/plugins/automaterial.rst +++ b/docs/plugins/automaterial.rst @@ -1,33 +1,45 @@ automaterial ============ -This makes building constructions (walls, floors, fortifications, etc) a little bit -easier by saving you from having to trawl through long lists of materials each time -you place one. -Firstly, it moves the last used material for a given construction type to the top of -the list, if there are any left. So if you build a wall with chalk blocks, the next -time you place a wall the chalk blocks will be at the top of the list, regardless of -distance (it only does this in "grouped" mode, as individual item lists could be huge). -This should mean you can place most constructions without having to search for your -preferred material type. +Sorts building materials by recent usage. This makes building constructions +(walls, floors, fortifications, etc) much easier by saving you from having to +trawl through long lists of materials each time you place one. + +It moves the last used material for a given construction type to the top of the +list, if there are any left. So if you build a wall with chalk blocks, the next +time you place a wall the chalk blocks will be at the top of the list, +regardless of distance (it only does this in "grouped" mode, as individual item +lists could be huge). This means you can place most constructions without having +to search for your preferred material type. + +Usage:: + + enable automaterial .. image:: ../images/automaterial-mat.png -Pressing :kbd:`a` while highlighting any material will enable that material for "auto select" -for this construction type. You can enable multiple materials as autoselect. Now the next -time you place this type of construction, the plugin will automatically choose materials -for you from the kinds you enabled. If there is enough to satisfy the whole placement, -you won't be prompted with the material screen - the construction will be placed and you -will be back in the construction menu as if you did it manually. +Pressing :kbd:`a` while highlighting any material will enable that material for +"auto select" for this construction type. You can enable multiple materials. Now +the next time you place this type of construction, the plugin will automatically +choose materials for you from the kinds you enabled. If there is enough to +satisfy the whole placement, you won't be prompted with the material screen at +all -- the construction will be placed and you will be back in the construction +menu. When choosing the construction placement, you will see a couple of options: .. image:: ../images/automaterial-pos.png -Use :kbd:`a` here to temporarily disable the material autoselection, e.g. if you need -to go to the material selection screen so you can toggle some materials on or off. +Use :kbd:`a` here to temporarily disable the material autoselection, e.g. if you +need to go to the material selection screen so you can toggle some materials on +or off. + +The other option (auto type selection, off by default) can be toggled on with +:kbd:`t`. If you toggle this option on, instead of returning you to the main +construction menu after selecting materials, it returns you back to this screen. +If you use this along with several autoselect enabled materials, you should be +able to place complex constructions more conveniently. -The other option (auto type selection, off by default) can be toggled on with :kbd:`t`. If you -toggle this option on, instead of returning you to the main construction menu after selecting -materials, it returns you back to this screen. If you use this along with several autoselect -enabled materials, you should be able to place complex constructions more conveniently. +The ``automaterial`` plugin also enables extra contruction placement modes, such +as designating areas larger than 10x10 and allowing you to designate hollow +rectangles instead of the default filled ones. From fd84fdce73789f0d0f956c113a1ec02088d21bbe Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 16:13:20 -0700 Subject: [PATCH 236/854] update docs for automelt --- docs/plugins/automelt.rst | 12 +++++++++--- plugins/automelt.cpp | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/plugins/automelt.rst b/docs/plugins/automelt.rst index 73f8ba502..749aac28a 100644 --- a/docs/plugins/automelt.rst +++ b/docs/plugins/automelt.rst @@ -1,5 +1,11 @@ automelt ======== -When automelt is enabled for a stockpile, any meltable items placed -in it will be designated to be melted. -This plugin adds an option to the :kbd:`q` menu when `enabled `. + +Quickly designate items to be melted. When `enabled `, this plugin adds +an option to the :kbd:`q` menu for stockpiles. When the ``automelt`` option is +selected for the stockpile, any items placed in the stockpile will automatically +be designated to be melted. + +Usage:: + + enable automelt diff --git a/plugins/automelt.cpp b/plugins/automelt.cpp index 852b324d6..e4033ed63 100644 --- a/plugins/automelt.cpp +++ b/plugins/automelt.cpp @@ -297,9 +297,9 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back( - PluginCommand( - "automelt", "Automatically melt metal items in marked stockpiles.", - automelt_cmd, false, "")); + PluginCommand("automelt", + "Automatically melt metal items in marked stockpiles.", + automelt_cmd)); return CR_OK; } From 08154ca1b5c1e0c635ceda48edad9dbaa3e2adee Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 16:13:27 -0700 Subject: [PATCH 237/854] update docs for autotrade --- docs/plugins/autotrade.rst | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/plugins/autotrade.rst b/docs/plugins/autotrade.rst index 4f0d95efd..e695a0a53 100644 --- a/docs/plugins/autotrade.rst +++ b/docs/plugins/autotrade.rst @@ -1,5 +1,11 @@ autotrade ========= -When autotrade is enabled for a stockpile, any items placed in it will be -designated to be taken to the Trade Depot whenever merchants are on the map. -This plugin adds an option to the :kbd:`q` menu when `enabled `. + +Quickly designate items to be traded. When `enabled `, this plugin adds +an option to the :kbd:`q` menu for stockpiles. When the ``autotrade`` option is +selected for the stockpile, any items placed in the stockpile will automatically +be designated to be traded. + +Usage:: + + enable autotrade From 2bc6e09ba0183ade4c483b353be0c965c56ab81a Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 16:20:43 -0700 Subject: [PATCH 238/854] fix formatting typos --- docs/plugins/autodump.rst | 39 ------------------------------------- docs/plugins/autofarm.rst | 8 ++++---- docs/plugins/autohauler.rst | 1 + 3 files changed, 5 insertions(+), 43 deletions(-) diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst index 8064583ea..83b50e498 100644 --- a/docs/plugins/autodump.rst +++ b/docs/plugins/autodump.rst @@ -41,42 +41,3 @@ Examples: Teleports all unforbidden items marked for dumping to the cursor position. - ``autodump destroy`` Destroys all unforbidden items marked for dumping - - - Alias ``autodump-destroy-here``, for keybindings. - :dfhack-keybind:`autodump-destroy-here` - -``autodump-destroy-item`` destroys the selected item, which may be selected -in the :kbd:`k` list, or inside a container. If called again before the game -is resumed, cancels destruction of the item. -:dfhack-keybind:`autodump-destroy-item` - - - - commands.push_back(PluginCommand( - "autodump", "Teleport items marked for dumping to the cursor.", - df_autodump, false, - " This utility lets you quickly move all items designated to be dumped.\n" - " Items are instantly moved to the cursor position, the dump flag is unset,\n" - " and the forbid flag is set, as if it had been dumped normally.\n" - " Be aware that any active dump item tasks still point at the item.\n" - "Options:\n" - " destroy - instead of dumping, destroy the items instantly.\n" - " destroy-here - only affect the tile under cursor.\n" - " visible - only process items that are not hidden.\n" - " hidden - only process hidden items.\n" - " forbidden - only process forbidden items (default: only unforbidden).\n" - )); - commands.push_back(PluginCommand( - "autodump-destroy-here", "Destroy items marked for dumping under cursor.", - df_autodump_destroy_here, Gui::cursor_hotkey, - " Identical to autodump destroy-here, but intended for use as keybinding.\n" - )); - commands.push_back(PluginCommand( - "autodump-destroy-item", "Destroy the selected item.", - df_autodump_destroy_item, Gui::any_item_hotkey, - " Destroy the selected item. The item may be selected\n" - " in the 'k' list, or inside a container. If called\n" - " again before the game is resumed, cancels destroy.\n" - )); - return CR_OK; diff --git a/docs/plugins/autofarm.rst b/docs/plugins/autofarm.rst index d5bb12e41..d66830fe0 100644 --- a/docs/plugins/autofarm.rst +++ b/docs/plugins/autofarm.rst @@ -10,13 +10,13 @@ Usage: - ``enable autofarm`` Enable the plugin and start managing crop assignment. -* ``autofarm runonce`` +- ``autofarm runonce`` Updates all farm plots once, without enabling the plugin. -* ``autofarm status`` +- ``autofarm status`` Prints status information, including any defined thresholds. -* ``autofarm default `` +- ``autofarm default `` Sets the default threshold. -* ``autofarm threshold [ ...]`` +- ``autofarm threshold [ ...]`` Sets thresholds of individual plant types. You can find the identifiers for the crop types in your world by running the diff --git a/docs/plugins/autohauler.rst b/docs/plugins/autohauler.rst index d1743f5ef..3bdd00950 100644 --- a/docs/plugins/autohauler.rst +++ b/docs/plugins/autohauler.rst @@ -41,6 +41,7 @@ Usage: In the next cycle, output the state of every dwarf. Examples: + - ``autohauler HAUL_STONE haulers`` Set stone hauling as a hauling labor (this is already the default). - ``autohauler RECOVER_WOUNDED allow`` From 154dc7c96c7760dbf5e62eb23bb62ac4bb9eb48a Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 16:32:43 -0700 Subject: [PATCH 239/854] update docs for blueprint --- docs/plugins/blueprint.rst | 108 ++++++++++++++++++++----------------- plugins/blueprint.cpp | 5 +- 2 files changed, 62 insertions(+), 51 deletions(-) diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst index 6d5e306d1..edfc1f096 100644 --- a/docs/plugins/blueprint.rst +++ b/docs/plugins/blueprint.rst @@ -1,7 +1,9 @@ blueprint ========= -The ``blueprint`` command exports the structure of a portion of your fortress in -a blueprint file that you (or anyone else) can later play back with `quickfort`. + +Record a live game map in a quickfort blueprint. With ``blueprint``, you can +export the structure of a portion of your fortress in a blueprint file that you +(or anyone else) can later play back with `quickfort`. Blueprints are ``.csv`` or ``.xlsx`` files created in the ``blueprints`` subdirectory of your DF folder. The map area to turn into a blueprint is either @@ -9,104 +11,110 @@ selected interactively with the ``blueprint gui`` command or, if the GUI is not used, starts at the active cursor location and extends right and down for the requested width and height. -**Usage:** - - ``blueprint [] [ []] []`` +Usage:: - ``blueprint gui [ []] []`` + blueprint [] [ []] [] + blueprint gui [ []] [] -**Examples:** +Examples: -``blueprint gui`` +- ``blueprint gui`` Runs `gui/blueprint`, the interactive frontend, where all configuration for a ``blueprint`` command can be set visually and interactively. - -``blueprint 30 40 bedrooms`` +- ``blueprint 30 40 bedrooms`` Generates blueprints for an area 30 tiles wide by 40 tiles tall, starting - from the active cursor on the current z-level. Blueprints are written - sequentially to ``bedrooms.csv`` in the ``blueprints`` directory. - -``blueprint 30 40 bedrooms dig --cursor 108,100,150`` + from the active cursor on the current z-level. Blueprints are written to + ``bedrooms.csv`` in the ``blueprints`` directory. +- ``blueprint 30 40 bedrooms dig --cursor 108,100,150`` Generates only the ``#dig`` blueprint in the ``bedrooms.csv`` file, and the start of the blueprint area is set to a specific value instead of using the in-game cursor position. -**Positional Parameters:** +Positional Parameters: -:``width``: Width of the area (in tiles) to translate. -:``height``: Height of the area (in tiles) to translate. -:``depth``: Number of z-levels to translate. Positive numbers go *up* from the - cursor and negative numbers go *down*. Defaults to 1 if not specified, - indicating that the blueprint should only include the current z-level. -:``name``: Base name for blueprint files created in the ``blueprints`` - directory. If no name is specified, "blueprint" is used by default. The - string must contain some characters other than numbers so the name won't be - confused with the optional ``depth`` parameter. +- ``width`` + Width of the area (in tiles) to translate. +- ``height`` + Height of the area (in tiles) to translate. +- ``depth`` + Number of z-levels to translate. Positive numbers go *up* from the cursor + and negative numbers go *down*. Defaults to 1 if not specified, indicating + that the blueprint should only include the current z-level. +- ``name`` + Base name for blueprint files created in the ``blueprints`` directory. If no + name is specified, "blueprint" is used by default. The string must contain + some characters other than numbers so the name won't be confused with the + optional ``depth`` parameter. -**Phases:** +Phases: If you want to generate blueprints only for specific phases, add their names to the commandline, anywhere after the blueprint base name. You can list multiple phases; just separate them with a space. -:``dig``: Generate quickfort ``#dig`` blueprints for digging natural stone. -:``carve``: Generate quickfort ``#dig`` blueprints for smoothing and carving. -:``build``: Generate quickfort ``#build`` blueprints for constructions and - buildings. -:``place``: Generate quickfort ``#place`` blueprints for placing stockpiles. -:``zone``: Generate quickfort ``#zone`` blueprints for designating zones. -:``query``: Generate quickfort ``#query`` blueprints for configuring rooms. +- ``dig`` + Generate quickfort ``#dig`` blueprints for digging natural stone. +- ``carve`` + Generate quickfort ``#dig`` blueprints for smoothing and carving. +- ``build`` + Generate quickfort ``#build`` blueprints for constructions and buildings. +- ``place`` + Generate quickfort ``#place`` blueprints for placing stockpiles. +- ``zone`` + Generate quickfort ``#zone`` blueprints for designating zones. +- ``query`` + Generate quickfort ``#query`` blueprints for configuring rooms. If no phases are specified, phases are autodetected. For example, a ``#place`` blueprint will be created only if there are stockpiles in the blueprint area. -**Options:** +Options: -``-c``, ``--cursor ,,``: +- ``-c``, ``--cursor ,,`` Use the specified map coordinates instead of the current cursor position for the upper left corner of the blueprint range. If this option is specified, then an active game map cursor is not necessary. -``-e``, ``--engrave``: +- ``-e``, ``--engrave`` Record engravings in the ``carve`` phase. If this option is not specified, engravings are ignored. -``-f``, ``--format ``: +- ``-f``, ``--format `` Select the output format of the generated files. See the ``Output formats`` section below for options. If not specified, the output format defaults to "minimal", which will produce a small, fast ``.csv`` file. -``-h``, ``--help``: +- ``-h``, ``--help`` Show command help text. -``-s``, ``--playback-start ,,``: +- ``-s``, ``--playback-start ,,`` Specify the column and row offsets (relative to the upper-left corner of the blueprint, which is ``1,1``) where the player should put the cursor when the blueprint is played back with `quickfort`, in `quickfort start marker ` format, for example: ``10,10,central stairs``. If there is a space in the comment, you will need - to surround the parameter string in double quotes: ``"-s10,10,central stairs"`` or - ``--playback-start "10,10,central stairs"`` or - ``"--playback-start=10,10,central stairs"``. -``-t``, ``--splitby ``: + to surround the parameter string in double quotes: + ``"-s10,10,central stairs"`` or ``--playback-start "10,10,central stairs"`` + or ``"--playback-start=10,10,central stairs"``. +- ``-t``, ``--splitby `` Split blueprints into multiple files. See the ``Splitting output into multiple files`` section below for details. If not specified, defaults to "none", which will create a standard quickfort `multi-blueprint ` file. -**Output formats:** +Output formats: Here are the values that can be passed to the ``--format`` flag: -:``minimal``: +- ``minimal`` Creates ``.csv`` files with minimal file size that are fast to read and write. This is the default. -:``pretty``: - Makes the blueprints in the ``.csv`` files easier to read and edit with a text - editor by adding extra spacing and alignment markers. +- ``pretty`` + Makes the blueprints in the ``.csv`` files easier to read and edit with a + text editor by adding extra spacing and alignment markers. -**Splitting output into multiple files:** +Splitting output into multiple files: The ``--splitby`` flag can take any of the following values: -:``none``: +- ``none`` Writes all blueprints into a single file. This is the standard format for quickfort fortress blueprint bundles and is the default. -:``phase``: +- ``phase`` Creates a separate file for each phase. diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 1401333eb..3a0cb60b4 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -118,7 +118,10 @@ struct_identity blueprint_options::_identity(sizeof(blueprint_options), &df::all command_result blueprint(color_ostream &, vector &); DFhackCExport command_result plugin_init(color_ostream &, vector &commands) { - commands.push_back(PluginCommand("blueprint", "Record the structure of a live game map in a quickfort blueprint file", blueprint, false)); + commands.push_back( + PluginCommand("blueprint", + "Record a live game map in a quickfort blueprint.", + blueprint)); return CR_OK; } From 629c22b148422916bd2544d5ff03b7972b4f40cf Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 16:38:28 -0700 Subject: [PATCH 240/854] update docs for building-hacks --- docs/Lua API.rst | 6 +++--- docs/plugins/building-hacks.rst | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 docs/plugins/building-hacks.rst diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 107a0ba2e..aba0bcab7 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4287,10 +4287,10 @@ blueprint files: The names of the functions are also available as the keys of the ``valid_phases`` table. -.. _building-hacks: +.. _building-hacks-api: -building-hacks -============== +building-hacks API +================== This plugin overwrites some methods in workshop df class so that mechanical workshops are possible. Although plugin export a function it's recommended to use lua decorated function. diff --git a/docs/plugins/building-hacks.rst b/docs/plugins/building-hacks.rst new file mode 100644 index 000000000..950d1751a --- /dev/null +++ b/docs/plugins/building-hacks.rst @@ -0,0 +1,11 @@ +building-hacks +============== + +Allows mods to create and manage powered workshops. + +Usage:: + + enable building-hacks + +Please see the `building-hacks-api` for information on accessing this plugin +from Lua scripts. From 19a4accca4c247ccd2f6c8999df5154d2cab0e4b Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 16:58:01 -0700 Subject: [PATCH 241/854] update docs for bulidingplan --- docs/plugins/buildingplan.rst | 136 +++++++++++++++++----------------- plugins/buildingplan.cpp | 6 +- 2 files changed, 70 insertions(+), 72 deletions(-) diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index ca6273d10..4b21da8af 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -1,88 +1,86 @@ buildingplan ============ -When active (via ``enable buildingplan``), this plugin adds a planning mode for -building placement. You can then place furniture, constructions, and other buildings -before the required materials are available, and they will be created in a suspended -state. Buildingplan will periodically scan for appropriate items, and the jobs will -be unsuspended when the items are available. -This is very useful when combined with `workflow` - you can set a constraint -to always have one or two doors/beds/tables/chairs/etc available, and place -as many as you like. The plugins then take over and fulfill the orders, -with minimal space dedicated to stockpiles. +Plan building construction before you have materials. This plugin adds a +planning mode for building placement. You can then place furniture, +constructions, and other buildings before the required materials are available, +and they will be created in a suspended state. Buildingplan will periodically +scan for appropriate items, and the jobs will be unsuspended when the items are +available. -.. _buildingplan-filters: - -Item filtering --------------- - -While placing a building, you can set filters for what materials you want the building made -out of, what quality you want the component items to be, and whether you want the items to -be decorated. - -If a building type takes more than one item to construct, use :kbd:`Ctrl`:kbd:`Left` and -:kbd:`Ctrl`:kbd:`Right` to select the item that you want to set filters for. Any filters that -you set will be used for all buildings of the selected type placed from that point onward -(until you set a new filter or clear the current one). Buildings placed before the filters -were changed will keep the filter values that were set when the building was placed. +This is very useful when combined with manager work orders or `workflow` -- you +can set a constraint to always have one or two doors/beds/tables/chairs/etc. +available, and place as many as you like. Materials are used to build the +planned buildings as they are produced, with minimal space dedicated to +stockpiles. -For example, you can be sure that all your constructed walls are the same color by setting -a filter to accept only certain types of stone. +Usage:: -Quickfort mode --------------- - -If you use the external Python Quickfort to apply building blueprints instead of the native -DFHack `quickfort` script, you must enable Quickfort mode. This temporarily enables -buildingplan for all building types and adds an extra blank screen after every building -placement. This "dummy" screen is needed for Python Quickfort to interact successfully with -Dwarf Fortress. + enable buildingplan + buildingplan set + buildingplan set true|false -Note that Quickfort mode is only for compatibility with the legacy Python Quickfort. The -DFHack `quickfort` script does not need Quickfort mode to be enabled. The `quickfort` script -will successfully integrate with buildingplan as long as the buildingplan plugin is enabled. +Running ``buildingplan set`` without parameters displays the current settings. .. _buildingplan-settings: Global settings --------------- -The buildingplan plugin has several global settings that can be set from the UI (:kbd:`G` -from any building placement screen, for example: :kbd:`b`:kbd:`a`:kbd:`G`). These settings -can also be set from the ``DFHack#`` prompt once a map is loaded (or from your -``onMapLoad.init`` file) with the syntax:: +The buildingplan plugin has global settings that can be set from the UI +(:kbd:`G` from any building placement screen, for example: +:kbd:`b`:kbd:`a`:kbd:`G`). These settings can also be set via the +``buildingplan set`` command. The available settings are: + +- ``all_enabled`` (default: false) + Enable planning mode for all building types. +- ``blocks``, ``boulders``, ``logs``, ``bars`` (defaults: true, true, true, false) + Allow blocks, boulders, logs, or bars to be matched for generic "building + material" items. +- ``quickfort_mode`` (default: false) + Enable compatibility mode for the legacy Python Quickfort (this setting is + not required for DFHack `quickfort`) + +The settings for ``blocks``, ``boulders``, ``logs``, and ``bars`` are saved with +your fort, so you only have to set them once and they will be persisted in your +save. + +If you normally embark with some blocks on hand for early workshops, you might +want to add this line to your ``dfhack-config/init/onMapLoad.init`` file to +always configure buildingplan to just use blocks for buildlings and +constructions:: - buildingplan set + on-new-fortress buildingplan set boulders false; buildingplan set logs false -and displayed with:: +.. _buildingplan-filters: - buildingplan set +Item filtering +-------------- -The available settings are: - -+----------------+---------+-----------+---------------------------------------+ -| Setting | Default | Persisted | Description | -+================+=========+===========+=======================================+ -| all_enabled | false | no | Enable planning mode for all building | -| | | | types. | -+----------------+---------+-----------+---------------------------------------+ -| blocks | true | yes | Allow blocks, boulders, logs, or bars | -+----------------+---------+ | to be matched for generic "building | -| boulders | true | | material" items | -+----------------+---------+ | | -| logs | true | | | -+----------------+---------+ | | -| bars | false | | | -+----------------+---------+-----------+---------------------------------------+ -| quickfort_mode | false | no | Enable compatibility mode for the | -| | | | legacy Python Quickfort (not required | -| | | | for DFHack quickfort) | -+----------------+---------+-----------+---------------------------------------+ - -For example, to ensure you only use blocks when a "building material" item is required, you -could add this to your ``onMapLoad.init`` file:: +While placing a building, you can set filters for what materials you want the +building made out of, what quality you want the component items to be, and +whether you want the items to be decorated. - on-new-fortress buildingplan set boulders false; buildingplan set logs false +If a building type takes more than one item to construct, use +:kbd:`Ctrl`:kbd:`Left` and :kbd:`Ctrl`:kbd:`Right` to select the item that you +want to set filters for. Any filters that you set will be used for all buildings +of the selected type placed from that point onward (until you set a new filter +or clear the current one). Buildings placed before the filters were changed will +keep the filter values that were set when the building was placed. + +For example, you can be sure that all your constructed walls are the same color +by setting a filter to accept only certain types of stone. + +Quickfort mode +-------------- + +If you use the external Python Quickfort to apply building blueprints instead of +the native DFHack `quickfort` script, you must enable Quickfort mode. This +temporarily enables buildingplan for all building types and adds an extra blank +screen after every building placement. This "dummy" screen is needed for Python +Quickfort to interact successfully with Dwarf Fortress. -Persisted settings (i.e. ``blocks``, ``boulders``, ``logs``, and ``bars``) are saved with -your game, so you only need to set them to the values you want once. +Note that Quickfort mode is only for compatibility with the legacy Python +Quickfort. The DFHack `quickfort` script does not need this Quickfort mode to be +enabled. The `quickfort` script will successfully integrate with buildingplan as +long as the buildingplan plugin itself is enabled. diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 594eaf48f..b13c8daa3 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1058,9 +1058,9 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back( - PluginCommand( - "buildingplan", "Plan building construction before you have materials", - buildingplan_cmd, false, "Run 'buildingplan debug [on|off]' to toggle debugging, or 'buildingplan version' to query the plugin version.")); + PluginCommand("buildingplan", + "Plan building construction before you have materials.", + buildingplan_cmd)); return CR_OK; } From 66b7bcaf1a6965f27e6d3bce1fcdd012c06ac1b3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 18 Jul 2022 17:09:48 -0700 Subject: [PATCH 242/854] update docs for burrows --- docs/plugins/burrows.rst | 61 ++++++++++++++++++++++------------------ plugins/burrows.cpp | 33 +++------------------- 2 files changed, 38 insertions(+), 56 deletions(-) diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst index 787b3ff37..658b7d209 100644 --- a/docs/plugins/burrows.rst +++ b/docs/plugins/burrows.rst @@ -1,38 +1,45 @@ burrows ======= -Miscellaneous burrow control. Allows manipulating burrows and automated burrow -expansion while digging. -Options: +Quick commands for burrow control. Allows manipulating burrows and automated +burrow expansion while digging. -:enable feature ...: - Enable features of the plugin. -:disable feature ...: - Disable features of the plugin. -:clear-unit burrow burrow ...: - Remove all units from the burrows. -:clear-tiles burrow burrow ...: - Remove all tiles from the burrows. -:set-units target-burrow src-burrow ...: - Clear target, and adds units from source burrows. -:add-units target-burrow src-burrow ...: +Usage: + +- ``enable auto-grow`` + When a wall inside a burrow with a name ending in '+' is dug out, the burrow + will be extended to newly-revealed adjacent walls. This final '+' may be + omitted in burrow name args of other ``burrows`` commands. Note that digging + 1-wide corridors with the miner inside the burrow is SLOW. +- ``disable auto-grow`` + Disables auto-grow processing. +- ``clear-unit [ ...]`` + Remove all units from the named burrows. +- ``clear-tiles [ ...]`` + Remove all tiles from the named burrows. +- ``set-units target-burrow [ ...]`` + Clear all units from the target burrow, then add units from the named source + burrows. +- ``add-units target-burrow [ ...]`` Add units from the source burrows to the target. -:remove-units target-burrow src-burrow ...: +- ``remove-units target-burrow [ ...]`` Remove units in source burrows from the target. -:set-tiles target-burrow src-burrow ...: - Clear target and adds tiles from the source burrows. -:add-tiles target-burrow src-burrow ...: +- ``set-tiles target-burrow [ ...]`` + Clear target burrow tiles and adds tiles from the names source burrows. +- ``add-tiles target-burrow [ ...]`` Add tiles from the source burrows to the target. -:remove-tiles target-burrow src-burrow ...: +- ``remove-tiles target-burrow [ ...]`` Remove tiles in source burrows from the target. - For these three options, in place of a source burrow it is - possible to use one of the following keywords: ABOVE_GROUND, - SUBTERRANEAN, INSIDE, OUTSIDE, LIGHT, DARK, HIDDEN, REVEALED +In place of a source burrow, you can use one of the following keywords: -Features: +- ``ABOVE_GROUND`` +- ``SUBTERRANEAN`` +- ``INSIDE`` +- ``OUTSIDE`` +- ``LIGHT`` +- ``DARK`` +- ``HIDDEN`` +- ``REVEALED`` -:auto-grow: When a wall inside a burrow with a name ending in '+' is dug - out, the burrow is extended to newly-revealed adjacent walls. - This final '+' may be omitted in burrow name args of commands above. - Digging 1-wide corridors with the miner inside the burrow is SLOW. +to add tiles with the given properties. diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp index 3b1d3fe86..029b3c715 100644 --- a/plugins/burrows.cpp +++ b/plugins/burrows.cpp @@ -53,35 +53,10 @@ static void deinit_map(color_ostream &out); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand( - "burrow", "Miscellaneous burrow control.", burrow, false, - " burrow enable options...\n" - " burrow disable options...\n" - " Enable or disable features of the plugin.\n" - " See below for a list and explanation.\n" - " burrow clear-units burrow burrow...\n" - " burrow clear-tiles burrow burrow...\n" - " Removes all units or tiles from the burrows.\n" - " burrow set-units target-burrow src-burrow...\n" - " burrow add-units target-burrow src-burrow...\n" - " burrow remove-units target-burrow src-burrow...\n" - " Adds or removes units in source burrows to/from the target\n" - " burrow. Set is equivalent to clear and add.\n" - " burrow set-tiles target-burrow src-burrow...\n" - " burrow add-tiles target-burrow src-burrow...\n" - " burrow remove-tiles target-burrow src-burrow...\n" - " Adds or removes tiles in source burrows to/from the target\n" - " burrow. In place of a source burrow it is possible to use\n" - " one of the following keywords:\n" - " ABOVE_GROUND, SUBTERRANEAN, INSIDE, OUTSIDE,\n" - " LIGHT, DARK, HIDDEN, REVEALED\n" - "Implemented features:\n" - " auto-grow\n" - " When a wall inside a burrow with a name ending in '+' is dug\n" - " out, the burrow is extended to newly-revealed adjacent walls.\n" - " This final '+' may be omitted in burrow name args of commands above.\n" - " Note: Digging 1-wide corridors with the miner inside the burrow is SLOW.\n" - )); + commands.push_back( + PluginCommand("burrow", + "Quick commands for burrow control.", + burrow)); if (Core::getInstance().isMapLoaded()) init_map(out); From a20612b0a8325c2abdbec29fb1f202907d1558e0 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 16 Jul 2022 10:17:46 -0600 Subject: [PATCH 243/854] Update structures --- library/xml | 2 +- plugins/strangemood.cpp | 214 ++++++++-------------------------------- 2 files changed, 43 insertions(+), 173 deletions(-) diff --git a/library/xml b/library/xml index 219676497..df19b880f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 2196764977011991127244b28ff13b90cef19af3 +Subproject commit df19b880fb3cbaa1a31a12b058acf9936d7ddada diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 5050b3621..de7fa426d 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -153,145 +153,40 @@ void selectWord (const df::language_word_table &table, int32_t &word, df::part_o } } -void generateName(df::language_name &output, int language, df::language_name_type mode, const df::language_word_table &table1, const df::language_word_table &table2) +void generateName(df::language_name &output, int language, const df::language_word_table &table1, const df::language_word_table &table2) { for (int i = 0; i < 100; i++) { - if (mode != 8 && mode != 9) - { - output = df::language_name(); - if (language == -1) - language = rng.df_trandom(world->raws.language.translations.size()); - output.type = mode; - output.language = language; - } + output = df::language_name(); + if (language == -1) + language = rng.df_trandom(world->raws.language.translations.size()); + output.type = language_name_type::Artifact; + output.language = language; output.has_name = 1; if (output.language == -1) output.language = rng.df_trandom(world->raws.language.translations.size()); int r, r2, r3; - switch (mode) + r = rng.df_trandom(3); + if (r == 0 || r == 1) { - case language_name_type::Figure: - case language_name_type::FigureNoFirst: - case language_name_type::FigureFirstOnly: - if (mode != 9) - { - int32_t word; df::part_of_speech part; - output.first_name.clear(); - selectWord(table1, word, part, 2); - if (word >= 0 && size_t(word) < world->raws.language.words.size()) - output.first_name = *world->raws.language.translations[language]->words[word]; - } - if (mode != 10) - { - case language_name_type::Site: - case language_name_type::Monument: - if (rng.df_trandom(2)) - { - selectWord(table2, output.words[0], output.parts_of_speech[0], 0); - selectWord(table1, output.words[1], output.parts_of_speech[1], 1); - } - else - { - selectWord(table1, output.words[0], output.parts_of_speech[0], 0); - selectWord(table2, output.words[1], output.parts_of_speech[1], 1); - } - } - break; - - case language_name_type::Artifact: - case language_name_type::Unk13: - case language_name_type::River: - r = rng.df_trandom(3); - if (r == 0 || r == 1) - { - if (rng.df_trandom(2)) - { - selectWord(table2, output.words[0], output.parts_of_speech[0], 0); - selectWord(table1, output.words[1], output.parts_of_speech[1], 1); - } - else - { - selectWord(table1, output.words[0], output.parts_of_speech[0], 0); - selectWord(table2, output.words[1], output.parts_of_speech[1], 1); - } - } - if (r == 1 || r == 2) + if (rng.df_trandom(2)) { - case language_name_type::Squad: - case language_name_type::LegendaryFigure: - case language_name_type::ArtImage: // this is not a typo either - r2 = rng.df_trandom(2); - if (r2) - selectWord(table1, output.words[5], output.parts_of_speech[5], 2); - else - selectWord(table2, output.words[5], output.parts_of_speech[5], 2); - r3 = rng.df_trandom(3); - if (rng.df_trandom(50)) - r3 = rng.df_trandom(2); - switch (r3) - { - case 0: - case 2: - if (r3 == 2) - r2 = rng.df_trandom(2); - if (r2) - selectWord(table2, output.words[6], output.parts_of_speech[6], 5); - else - selectWord(table1, output.words[6], output.parts_of_speech[6], 5); - if (r3 == 0) - break; - r2 = -r2; - case 1: - if (r2) - selectWord(table1, output.words[2], output.parts_of_speech[2], 3); - else - selectWord(table2, output.words[2], output.parts_of_speech[2], 3); - if (!(rng.df_trandom(100))) - selectWord(table1, output.words[3], output.parts_of_speech[3], 3); - break; - } + selectWord(table2, output.words[0], output.parts_of_speech[0], 0); + selectWord(table1, output.words[1], output.parts_of_speech[1], 1); } - if (rng.df_trandom(100)) + else { - if (rng.df_trandom(2)) - selectWord(table1, output.words[4], output.parts_of_speech[4], 4); - else - selectWord(table2, output.words[4], output.parts_of_speech[4], 4); + selectWord(table1, output.words[0], output.parts_of_speech[0], 0); + selectWord(table2, output.words[1], output.parts_of_speech[1], 1); } - if ((mode == 3) && (output.parts_of_speech[5] == part_of_speech::Noun) && (output.words[5] != -1) && (world->raws.language.words[output.words[5]]->forms[1].length())) - output.parts_of_speech[5] = part_of_speech::NounPlural; - break; - - case language_name_type::Civilization: - case language_name_type::World: - case language_name_type::Region: - case language_name_type::AdventuringGroup: - case language_name_type::SiteGovernment: - case language_name_type::NomadicGroup: - case language_name_type::Vessel: - case language_name_type::MilitaryUnit: - case language_name_type::Religion: - case language_name_type::MountainPeak: - case language_name_type::Temple: - case language_name_type::Keep: - case language_name_type::MeadHall: - case language_name_type::Unk24: - case language_name_type::Unk25: - case language_name_type::Unk26: - case language_name_type::Market: - case language_name_type::Tavern: - case language_name_type::War: - case language_name_type::Battle: - case language_name_type::Siege: - case language_name_type::Road: - case language_name_type::Wall: - case language_name_type::Bridge: - case language_name_type::Tunnel: - case language_name_type::PretentiousEntityPosition: - case language_name_type::Tomb: - case language_name_type::OutcastGroup: - selectWord(table1, output.words[5], output.parts_of_speech[5], 2); + } + if (r == 1 || r == 2) + { + r2 = rng.df_trandom(2); + if (r2) + selectWord(table1, output.words[5], output.parts_of_speech[5], 2); + else + selectWord(table2, output.words[5], output.parts_of_speech[5], 2); r3 = rng.df_trandom(3); if (rng.df_trandom(50)) r3 = rng.df_trandom(2); @@ -299,56 +194,31 @@ void generateName(df::language_name &output, int language, df::language_name_typ { case 0: case 2: - selectWord(table2, output.words[6], output.parts_of_speech[6], 5); + if (r3 == 2) + r2 = rng.df_trandom(2); + if (r2) + selectWord(table2, output.words[6], output.parts_of_speech[6], 5); + else + selectWord(table1, output.words[6], output.parts_of_speech[6], 5); if (r3 == 0) break; + r2 = -r2; case 1: - selectWord(table2, output.words[2], output.parts_of_speech[2], 3); - if (!(rng.df_trandom(100))) - selectWord(table2, output.words[3], output.parts_of_speech[3], 3); - break; - } - if (rng.df_trandom(100)) - selectWord(table2, output.words[4], output.parts_of_speech[4], 4); - break; - - case language_name_type::Dungeon: - r = rng.df_trandom(3); - if (r == 0 || r == 1) - { - selectWord(table2, output.words[0], output.parts_of_speech[0], 0); - selectWord(table1, output.words[1], output.parts_of_speech[1], 1); - } - if (r == 1 || r == 2) - { - r2 = rng.df_trandom(2); - if (r == 2 || r2 == 1) - selectWord(table1, output.words[5], output.parts_of_speech[5], 2); + if (r2) + selectWord(table1, output.words[2], output.parts_of_speech[2], 3); else - selectWord(table2, output.words[5], output.parts_of_speech[5], 2); - r3 = rng.df_trandom(3); - if (rng.df_trandom(50)) - r3 = rng.df_trandom(2); - switch (r3) - { - case 0: - case 2: - selectWord(table1, output.words[6], output.parts_of_speech[6], 5); - if (r3 == 0) - break; - case 1: selectWord(table2, output.words[2], output.parts_of_speech[2], 3); - if (!(rng.df_trandom(100))) - selectWord(table2, output.words[3], output.parts_of_speech[3], 3); - break; - } + if (!(rng.df_trandom(100))) + selectWord(table1, output.words[3], output.parts_of_speech[3], 3); + break; } - if (rng.df_trandom(100)) + } + if (rng.df_trandom(100)) + { + if (rng.df_trandom(2)) + selectWord(table1, output.words[4], output.parts_of_speech[4], 4); + else selectWord(table2, output.words[4], output.parts_of_speech[4], 4); - break; - default: - // not handled yet - break; } if (output.words[2] != -1 && output.words[3] != -1 && world->raws.language.words[output.words[3]]->adj_dist < world->raws.language.words[output.words[2]]->adj_dist) @@ -1351,10 +1221,10 @@ command_result df_strangemood (color_ostream &out, vector & parameters) // Generate the artifact's name if (type == mood_type::Fell || type == mood_type::Macabre) - generateName(unit->status.artifact_name, unit->name.language, language_name_type::Artifact, world->raws.language.word_table[0][2], world->raws.language.word_table[1][2]); + generateName(unit->status.artifact_name, unit->name.language, world->raws.language.word_table[0][2], world->raws.language.word_table[1][2]); else { - generateName(unit->status.artifact_name, unit->name.language, language_name_type::Artifact, world->raws.language.word_table[0][1], world->raws.language.word_table[1][1]); + generateName(unit->status.artifact_name, unit->name.language, world->raws.language.word_table[0][1], world->raws.language.word_table[1][1]); if (!rng.df_trandom(100)) unit->status.artifact_name = unit->name; } From e5961b45b4ef0e3f236d17374c6a988584114e7f Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 19 Jul 2022 07:17:31 +0000 Subject: [PATCH 244/854] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index df19b880f..1595cc1fe 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit df19b880fb3cbaa1a31a12b058acf9936d7ddada +Subproject commit 1595cc1fe1f4662eebffdb4a77355491aafacea6 From e3588cf49c83ecd2f99af0ef0bd05853e1ffb394 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 19 Jul 2022 22:13:53 -0700 Subject: [PATCH 245/854] return entire tag data structure for iterating --- library/lua/helpdb.lua | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 18ba5b746..0c0bd9955 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -186,7 +186,9 @@ local function update_entry(entry, iterator, opts) table.insert(lines, line) ::continue:: end - entry.long_help = table.concat(lines, '\n') + if #lines > 0 then + entry.long_help = table.concat(lines, '\n') + end end -- create db entry based on parsing sphinx-rendered help text @@ -457,13 +459,13 @@ function get_tags() return set_to_sorted_list(tag_index) end --- returns the description associated with the given tag -function get_tag_description(tag) +function get_tag_data(tag) ensure_db() if not tag_index[tag] then - error('invalid tag: ' .. tag) + dfhack.printerr('invalid tag: ' .. tag) + return {} end - return tag_index[tag].description + return tag_index[tag] end --------------------------------------------------------------------------- @@ -621,7 +623,8 @@ function tags() local tags = get_tags() local width = get_max_width(tags, 10) for _,tag in ipairs(tags) do - print((' %-'..width..'s %s'):format(tag, get_tag_description(tag))) + print((' %-'..width..'s %s'):format(tag, + get_tag_data(tag).description)) end end From ddcb9b4921dcf28ecbe9c0aa4b62ac6dadfdbbed Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 19 Jul 2022 22:35:08 -0700 Subject: [PATCH 246/854] add missing 'man' builtin --- library/lua/helpdb.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 0c0bd9955..5e2b28ede 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -55,6 +55,7 @@ local BUILTINS = { 'kill-lua', 'load', 'ls', + 'man', 'plug', 'reload', 'script', From 954e2461409a7bad8bae88594cee0a114d516e3c Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 19 Jul 2022 23:01:25 -0700 Subject: [PATCH 247/854] reformat builtins and properly read tags --- docs/Tags.rst | 25 ++++++++++++++++--------- docs/builtins/alias.rst | 10 ++++++++-- docs/builtins/clear.rst | 5 ++++- docs/builtins/cls.rst | 7 +++++-- docs/builtins/devel/dump-rpc.rst | 5 ++++- docs/builtins/die.rst | 5 ++++- docs/builtins/dir.rst | 5 ++++- docs/builtins/disable.rst | 22 +++++++--------------- docs/builtins/enable.rst | 22 ++++++++++++++++------ docs/builtins/fpause.rst | 9 ++++++++- docs/builtins/help.rst | 12 +++++++++--- docs/builtins/hide.rst | 9 ++++++++- docs/builtins/keybinding.rst | 8 ++++++-- docs/builtins/kill-lua.rst | 15 ++++++++++++--- docs/builtins/load.rst | 5 ++++- docs/builtins/ls.rst | 8 ++++++-- docs/builtins/man.rst | 7 +++++++ docs/builtins/plug.rst | 5 ++++- docs/builtins/reload.rst | 5 ++++- docs/builtins/sc-script.rst | 8 ++++++-- docs/builtins/script.rst | 8 ++++++-- docs/builtins/show.rst | 9 ++++++++- docs/builtins/tags.rst | 5 ++++- docs/builtins/type.rst | 5 ++++- docs/builtins/unload.rst | 5 ++++- library/lua/helpdb.lua | 2 +- 26 files changed, 169 insertions(+), 62 deletions(-) create mode 100644 docs/builtins/man.rst diff --git a/docs/Tags.rst b/docs/Tags.rst index 7a5288cbf..56e5af650 100644 --- a/docs/Tags.rst +++ b/docs/Tags.rst @@ -5,12 +5,19 @@ Tags This is the list of tags and their descriptions. -- adventure: tools relevant to adventure mode -- fort: tools relevant to fort mode -- legends: tools relevant to legends mode -- enable: tools that are able to be enabled/disabled for some persistent effect -- items: tools that create or modify in-game items -- units: tools that create or modify units -- jobs: tools that create or modify jobs -- labors: tools that deal with labor assignment -- auto: tools that automatically manage some aspect of your fortress +- adventure: Tools relevant to adventure mode +- fort: Tools relevant to fort mode +- legends: Tools relevant to legends mode +- items: Tools that create or modify in-game items +- units: Tools that create or modify units +- jobs: Tools that create or modify jobs +- labors: Tools that deal with labor assignment +- auto: Tools that automatically manage some aspect of your fortress +- map: Map modification +- system: Tools related to working with DFHack commands or the core DFHack library +- productivity: Tools that help you do things that you could do manually, but using the tool is better and faster +- animals: Tools that help you manage animals +- fix: Tools that fix specific bugs +- inspection: Tools that let you inspect game data +- buildings/furniture: Tools that help you work wtih placing or configuring buildings and furniture +- quickfort: Tools that are involved in creating and playing back blueprints diff --git a/docs/builtins/alias.rst b/docs/builtins/alias.rst index 034d0f33a..325bb9dd7 100644 --- a/docs/builtins/alias.rst +++ b/docs/builtins/alias.rst @@ -1,5 +1,8 @@ alias ------ +===== + +Tags: system +:dfhack-keybind:`alias` Configure helper aliases for other DFHack commands. Aliases are resolved immediately after built-in commands, which means that an alias cannot override @@ -21,7 +24,10 @@ Usage: Aliases can be given additional arguments when created and invoked, which will be passed to the underlying command in order. -Example:: +Example +------- + +:: [DFHack]# alias add pargs devel/print-args example [DFHack]# pargs text diff --git a/docs/builtins/clear.rst b/docs/builtins/clear.rst index b964a8b3f..44c4f679a 100644 --- a/docs/builtins/clear.rst +++ b/docs/builtins/clear.rst @@ -1,4 +1,7 @@ clear ------ +===== + +Tags: system +:dfhack-keybind:`clear` Clear the terminal screen. This command is an alias for `cls`. diff --git a/docs/builtins/cls.rst b/docs/builtins/cls.rst index b05354d5a..0514353bd 100644 --- a/docs/builtins/cls.rst +++ b/docs/builtins/cls.rst @@ -1,5 +1,8 @@ cls ---- +=== -Clear the terminal screen. Can also be invoked as ``clear``. Note that this +Tags: system +:dfhack-keybind:`cls` + +Clear the terminal screen. Can also be invoked as `clear`. Note that this command does not delete command history. It just clears the text on the screen. diff --git a/docs/builtins/devel/dump-rpc.rst b/docs/builtins/devel/dump-rpc.rst index af173a179..e6a710409 100644 --- a/docs/builtins/devel/dump-rpc.rst +++ b/docs/builtins/devel/dump-rpc.rst @@ -1,5 +1,8 @@ devel/dump-rpc --------------- +============== + +Tags: system +:dfhack-keybind:`devel/dump-rpc` Writes RPC endpoint information to the specified file. diff --git a/docs/builtins/die.rst b/docs/builtins/die.rst index d2dae484f..7a7414b8c 100644 --- a/docs/builtins/die.rst +++ b/docs/builtins/die.rst @@ -1,4 +1,7 @@ die ---- +=== + +Tags: system +:dfhack-keybind:`die` Instantly exits DF without saving. diff --git a/docs/builtins/dir.rst b/docs/builtins/dir.rst index 5c2059099..2eca9218c 100644 --- a/docs/builtins/dir.rst +++ b/docs/builtins/dir.rst @@ -1,4 +1,7 @@ dir ---- +=== + +Tags: system +:dfhack-keybind:`dir` List available DFHack commands. This is an alias of the `ls` command. diff --git a/docs/builtins/disable.rst b/docs/builtins/disable.rst index d871ff06e..413b8325f 100644 --- a/docs/builtins/disable.rst +++ b/docs/builtins/disable.rst @@ -1,20 +1,12 @@ disable -------- +======= -Deactivate a DFHack tool that has some persistent effect. Many plugins and -scripts can be in a distinct enabled or disabled state. Some of them activate -and deactivate automatically depending on the contents of the world raws. Others -store their state in world data. However a number of them have to be enabled -globally, and the init file is the right place to do it. +Tags: system +:dfhack-keybind:`disable` -Most such plugins or scripts support the built-in ``enable`` and ``disable`` -commands. Calling them at any time without arguments prints a list of enabled -and disabled plugins, and shows whether that can be changed through the same -commands. Passing plugin names to these commands will enable or disable the -specified plugins. For example, to disable the `manipulator` plugin:: +Deactivate a DFHack tool that has some persistent effect. See the `enable` +command for more info. - disable manipulator +Usage:: -It is also possible to enable or disable multiple plugins at once:: - - disable manipulator search + disable [ ...] diff --git a/docs/builtins/enable.rst b/docs/builtins/enable.rst index 8338cc7f4..78534a98c 100644 --- a/docs/builtins/enable.rst +++ b/docs/builtins/enable.rst @@ -1,5 +1,8 @@ enable ------- +====== + +Tags: system +:dfhack-keybind:`enable` Activate a DFHack tool that has some persistent effect. Many plugins and scripts can be in a distinct enabled or disabled state. Some of them activate and @@ -7,14 +10,21 @@ deactivate automatically depending on the contents of the world raws. Others store their state in world data. However a number of them have to be enabled globally, and the init file is the right place to do it. -Most such plugins or scripts support the built-in ``enable`` and ``disable`` +Most such plugins or scripts support the built-in ``enable`` and `disable` commands. Calling them at any time without arguments prints a list of enabled and disabled plugins, and shows whether that can be changed through the same commands. Passing plugin names to these commands will enable or disable the -specified plugins. For example, to enable the `manipulator` plugin:: +specified plugins. + +Usage:: - enable manipulator + enable + enable [ ...] -It is also possible to enable or disable multiple plugins at once:: +Examples +-------- - enable manipulator search +- ``enable manipulator`` + Enable the ``manipulator`` plugin. +- ``enable manipulator search`` + Enable multiple plugins at once. diff --git a/docs/builtins/fpause.rst b/docs/builtins/fpause.rst index 68e4eb07e..60459848e 100644 --- a/docs/builtins/fpause.rst +++ b/docs/builtins/fpause.rst @@ -1,5 +1,12 @@ fpause ------- +====== + +Tags: system +:dfhack-keybind:`fpause` Forces DF to pause. This is useful when your FPS drops below 1 and you lose control of the game. + +Usage:: + + fpause diff --git a/docs/builtins/help.rst b/docs/builtins/help.rst index aa9e4b170..49c010fec 100644 --- a/docs/builtins/help.rst +++ b/docs/builtins/help.rst @@ -1,5 +1,8 @@ help ----- +==== + +Tags: system +:dfhack-keybind:`help` Display help about a command or plugin. @@ -8,7 +11,10 @@ Usage:: help|?|man help|?|man -Examples:: +Examples +-------- + +:: help blueprint man blueprint @@ -16,4 +22,4 @@ Examples:: Both examples above will display the help text for the `blueprint` command. Some commands also take ``help`` or ``?`` as an option on their command line -for the same effect - e.g. ``blueprint help``. +for the same effect -- e.g. ``blueprint help``. diff --git a/docs/builtins/hide.rst b/docs/builtins/hide.rst index 5c8d08452..6b391972f 100644 --- a/docs/builtins/hide.rst +++ b/docs/builtins/hide.rst @@ -1,8 +1,15 @@ hide ----- +==== + +Tags: system +:dfhack-keybind:`hide` Hides the DFHack terminal window. You can show it again with the `show` command, though you'll need to use it from a `keybinding` set beforehand or the in-game `command-prompt`. Only available on Windows. + +Usage:: + + hide diff --git a/docs/builtins/keybinding.rst b/docs/builtins/keybinding.rst index 7ab92c206..72ceaaf71 100644 --- a/docs/builtins/keybinding.rst +++ b/docs/builtins/keybinding.rst @@ -1,5 +1,8 @@ keybinding ----------- +========== + +Tags: system +:dfhack-keybind:`keybinding` Create hotkeys that will run DFHack commands. Like any other command it can be used at any time from the console, but bindings are not remembered between runs @@ -46,7 +49,8 @@ example, ``@foo|bar|baz/foo`` would match anything under ``@foo``, ``@bar``, or Interactive commands like `liquids` cannot be used as hotkeys. -Examples: +Examples +-------- - ``keybinding add Alt-F1 hotkeys`` Bind Alt-F1 to run the `hotkeys` command on any screen at any time. diff --git a/docs/builtins/kill-lua.rst b/docs/builtins/kill-lua.rst index ee354846a..875376952 100644 --- a/docs/builtins/kill-lua.rst +++ b/docs/builtins/kill-lua.rst @@ -1,6 +1,15 @@ kill-lua --------- +======== + +Tags: system +:dfhack-keybind:`kill-lua` Gracefully stops any currently-running Lua scripts. Use this command to stop -a misbehaving script that appears to be stuck. Use ``kill-lua force`` if just -``kill-lua`` doesn't work. +a misbehaving script that appears to be stuck. + +Usage:: + + kill-lua + kill-lua force + +Use ``kill-lua force`` if just ``kill-lua`` doesn't seem to work. diff --git a/docs/builtins/load.rst b/docs/builtins/load.rst index fb654b8a4..cac760c2a 100644 --- a/docs/builtins/load.rst +++ b/docs/builtins/load.rst @@ -1,5 +1,8 @@ load ----- +==== + +Tags: system +:dfhack-keybind:`load` Load and register a plugin library. Also see `unload` and `reload` for related actions. diff --git a/docs/builtins/ls.rst b/docs/builtins/ls.rst index fd2e532ed..6875083f5 100644 --- a/docs/builtins/ls.rst +++ b/docs/builtins/ls.rst @@ -1,5 +1,8 @@ ls --- +== + +Tags: system +:dfhack-keybind:`ls` List available DFHack commands. In order to group related commands, each command is associated with a list of tags. You can filter the listed commands by a tag @@ -25,7 +28,8 @@ You can also pass some optional parameters to change how ``ls`` behaves: - ``--dev`` Include commands intended for developers and modders. -Examples: +Examples +-------- - ``ls adventure`` Lists all commands with the ``adventure`` tag. diff --git a/docs/builtins/man.rst b/docs/builtins/man.rst new file mode 100644 index 000000000..3b5b52515 --- /dev/null +++ b/docs/builtins/man.rst @@ -0,0 +1,7 @@ +man +=== + +Tags: system +:dfhack-keybind:`man` + +An alias for the `help` command. diff --git a/docs/builtins/plug.rst b/docs/builtins/plug.rst index 93bd452a7..8f714b7cf 100644 --- a/docs/builtins/plug.rst +++ b/docs/builtins/plug.rst @@ -1,5 +1,8 @@ plug ----- +==== + +Tags: system +:dfhack-keybind:`plug` Lists available plugins and whether they are enabled. diff --git a/docs/builtins/reload.rst b/docs/builtins/reload.rst index 8f28e5ca6..d61e5c2fd 100644 --- a/docs/builtins/reload.rst +++ b/docs/builtins/reload.rst @@ -1,5 +1,8 @@ reload ------- +====== + +Tags: system +:dfhack-keybind:`reload` Reload a loaded plugin. Developer use this command to reload a plugin that they are actively modifying. Also see `load` and `unload` for related actions. diff --git a/docs/builtins/sc-script.rst b/docs/builtins/sc-script.rst index 20a0e9497..2c80e1130 100644 --- a/docs/builtins/sc-script.rst +++ b/docs/builtins/sc-script.rst @@ -1,5 +1,8 @@ sc-script ---------- +========= + +Tags: system +:dfhack-keybind:`sc-script` Runs commands when game state changes occur. This is similar to the static `init-files` but is slightly more flexible since it can be set dynamically. @@ -13,7 +16,8 @@ Usage: - ``sc-script add|remove [ ...]`` Register or unregister a file to be run for the specified event. -Examples: +Examples +-------- - ``sc-script add SC_MAP_LOADED spawn_extra_monsters.init`` Registers the ``spawn_extra_monsters.init`` file to be run whenever a new diff --git a/docs/builtins/script.rst b/docs/builtins/script.rst index ae4017ca7..d69f3bbc7 100644 --- a/docs/builtins/script.rst +++ b/docs/builtins/script.rst @@ -1,5 +1,8 @@ script ------- +====== + +Tags: system +:dfhack-keybind:`script` Executes a batch file of DFHack commands. It reads a text file and runs each line as a DFHack command as if it had been typed in by the user - treating the @@ -12,7 +15,8 @@ Usage:: script -Examples: +Examples +-------- - ``script startup.txt`` Executes the commands in ``startup.txt``, which exists in your DF game diff --git a/docs/builtins/show.rst b/docs/builtins/show.rst index 8a6f9b446..e192f857c 100644 --- a/docs/builtins/show.rst +++ b/docs/builtins/show.rst @@ -1,5 +1,8 @@ show ----- +==== + +Tags: system +:dfhack-keybind:`show` Unhides the DFHack terminal window. Useful if you have hidden the terminal with `hide` and you want it back. Since the terminal window won't be available to run @@ -7,3 +10,7 @@ this command, you'll need to use it from a `keybinding` set beforehand or the in-game `command-prompt`. Only available on Windows. + +Usage:: + + show diff --git a/docs/builtins/tags.rst b/docs/builtins/tags.rst index 9de0f8df3..3d7933fca 100644 --- a/docs/builtins/tags.rst +++ b/docs/builtins/tags.rst @@ -1,5 +1,8 @@ tags ----- +==== + +Tags: system +:dfhack-keybind:`tags` List the strings that DFHack tools can be tagged with. You can find groups of related tools by passing the tag name to the `ls` command. diff --git a/docs/builtins/type.rst b/docs/builtins/type.rst index c4c752bef..16325c90f 100644 --- a/docs/builtins/type.rst +++ b/docs/builtins/type.rst @@ -1,5 +1,8 @@ type ----- +==== + +Tags: system +:dfhack-keybind:`type` Describes how a command is implemented. DFHack commands can be provided by plugins, scripts, or by the core library itself. The ``type`` command can tell diff --git a/docs/builtins/unload.rst b/docs/builtins/unload.rst index f4069ed88..99eed500c 100644 --- a/docs/builtins/unload.rst +++ b/docs/builtins/unload.rst @@ -1,5 +1,8 @@ unload ------- +====== + +Tags: system +:dfhack-keybind:`unload` Unload a plugin from memory. Also see `load` and `reload` for related actions. diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 5e2b28ede..a972cca24 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -268,7 +268,7 @@ local function update_db(old_db, db, source, entry_name, kwargs) error('unhandled help source: ' .. source) end db[entry_name] = entry - for _,tag in ipairs(entry.tags) do + for tag in pairs(entry.tags) do -- ignore unknown tags if tag_index[tag] then table.insert(tag_index[tag], entry_name) From 1cad77601e15bf543ddab5326a3c38c665a63ced Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 19 Jul 2022 23:11:02 -0700 Subject: [PATCH 248/854] update formatting for plugin docs --- docs/plugins/3dveins.rst | 6 +++++- docs/plugins/add-spatter.rst | 3 +++ docs/plugins/autochop.rst | 3 +++ docs/plugins/autoclothing.rst | 6 +++++- docs/plugins/autodump-destroy-here.rst | 3 +++ docs/plugins/autodump-destroy-item.rst | 3 +++ docs/plugins/autodump.rst | 9 +++++++-- docs/plugins/autofarm.rst | 6 +++++- docs/plugins/autogems-reload.rst | 3 +++ docs/plugins/autogems.rst | 3 +++ docs/plugins/autohauler.rst | 6 +++++- docs/plugins/autolabor.rst | 9 +++++++-- docs/plugins/automaterial.rst | 3 +++ docs/plugins/automelt.rst | 3 +++ docs/plugins/autotrade.rst | 3 +++ docs/plugins/blueprint.rst | 21 +++++++++++++++------ docs/plugins/building-hacks.rst | 3 +++ docs/plugins/buildingplan.rst | 3 +++ docs/plugins/burrows.rst | 3 +++ 19 files changed, 85 insertions(+), 14 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 9a074a4c8..89c241df6 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -1,6 +1,9 @@ 3dveins ======= +Tags: +:dfhack-keybind:`3dveins` + Rewrites layer veins to expand in 3D space. Existing, flat veins are removed and new 3D veins that naturally span z-levels are generated in their place. The transformation preserves the mineral counts reported by `prospect`. @@ -11,7 +14,8 @@ Usage:: The ``verbose`` option prints out extra information to the console. -Example:: +Example +------- 3dveins diff --git a/docs/plugins/add-spatter.rst b/docs/plugins/add-spatter.rst index b6bb97cf7..92808017c 100644 --- a/docs/plugins/add-spatter.rst +++ b/docs/plugins/add-spatter.rst @@ -1,6 +1,9 @@ add-spatter =========== +Tags: +:dfhack-keybind:`add-spatter` + Make tagged reactions produce contaminants. The plugin is intended to give some use to all those poisons that can be bought from caravans. It automatically enables itself when you load a world with reactions that include names starting diff --git a/docs/plugins/autochop.rst b/docs/plugins/autochop.rst index 7c517e901..704b97143 100644 --- a/docs/plugins/autochop.rst +++ b/docs/plugins/autochop.rst @@ -1,6 +1,9 @@ autochop ======== +Tags: +:dfhack-keybind:`autochop` + Auto-harvest trees when low on stockpiled logs. This plugin can designate trees for chopping when your stocks are low on logs. diff --git a/docs/plugins/autoclothing.rst b/docs/plugins/autoclothing.rst index a3c53a65a..a8cc001dd 100644 --- a/docs/plugins/autoclothing.rst +++ b/docs/plugins/autoclothing.rst @@ -1,6 +1,9 @@ autoclothing ============ +Tags: +:dfhack-keybind:`autoclothing` + Automatically manage clothing work orders. It allows you to set how many of each clothing type every citizen should have. @@ -15,7 +18,8 @@ anything your civilization can produce, such as "dress" or "mitten". When invoked without a number, it shows the current configuration for that material and item. -Examples: +Examples +-------- * ``autoclothing cloth "short skirt" 10``: Sets the desired number of cloth short skirts available per citizen to 10. diff --git a/docs/plugins/autodump-destroy-here.rst b/docs/plugins/autodump-destroy-here.rst index 375a799c0..35356166c 100644 --- a/docs/plugins/autodump-destroy-here.rst +++ b/docs/plugins/autodump-destroy-here.rst @@ -1,6 +1,9 @@ autodump-destroy-here ===================== +Tags: +:dfhack-keybind:`autodump-destroy-here` + Destroy items marked for dumping under cursor. If called again before the game is resumed, cancels destruction of the items. This is an alias for the `autodump` command ``autodump destroy-here``, intended for use as a keybinding. diff --git a/docs/plugins/autodump-destroy-item.rst b/docs/plugins/autodump-destroy-item.rst index a9f3554c3..9447590d0 100644 --- a/docs/plugins/autodump-destroy-item.rst +++ b/docs/plugins/autodump-destroy-item.rst @@ -1,6 +1,9 @@ autodump-destroy-item ===================== +Tags: +:dfhack-keybind:`autodump-destroy-item` + Destroy the selected item. The item may be selected in the :kbd:`k` list or in the container item list. If called again before the game is resumed, cancels destruction of the item. diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst index 83b50e498..5ac0d1cd5 100644 --- a/docs/plugins/autodump.rst +++ b/docs/plugins/autodump.rst @@ -1,6 +1,9 @@ autodump ======== +Tags: +:dfhack-keybind:`autodump` + Quickly designate or teleport items to be dumped. When `enabled `, this plugin adds an option to the :kbd:`q` menu for stockpiles. When the ``autodump`` option is selected for the stockpile, any items placed in the stockpile will @@ -19,7 +22,8 @@ Usage:: enable autodump autodump [] -Options: +Options +------- - ``destroy`` Destroy instead of dumping. Doesn't require a cursor. If ``autodump`` is @@ -35,7 +39,8 @@ Options: - ``forbidden`` Only process forbidden items (default: only unforbidden). -Examples: +Examples +-------- - ``autodump`` Teleports all unforbidden items marked for dumping to the cursor position. diff --git a/docs/plugins/autofarm.rst b/docs/plugins/autofarm.rst index d66830fe0..9a3e16d43 100644 --- a/docs/plugins/autofarm.rst +++ b/docs/plugins/autofarm.rst @@ -1,6 +1,9 @@ autofarm ======== +Tags: +:dfhack-keybind:`autofarm` + Automatically manage farm crop selection. This plugin periodically scans your plant stocks and assigns crops to your farm plots based on which plant stocks are low (as long as you have the appropriate seeds). The target threshold for @@ -24,7 +27,8 @@ following command:: lua "for _,plant in ipairs(df.global.world.raws.plants.all) do if plant.flags.SEED then print(plant.id) end end" -Examples: +Examples +-------- - ``autofarm default 30`` Set the default threshold to 30. diff --git a/docs/plugins/autogems-reload.rst b/docs/plugins/autogems-reload.rst index a37117746..0988496cb 100644 --- a/docs/plugins/autogems-reload.rst +++ b/docs/plugins/autogems-reload.rst @@ -1,6 +1,9 @@ autogems-reload =============== +Tags: +:dfhack-keybind:`autogems-reload` + Reloads the autogems configuration file. You might need to do this if you have manually modified the contents while the game is running. diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index ad3b0f9c3..724752217 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -1,6 +1,9 @@ autogems ======== +Tags: +:dfhack-keybind:`autogems` + Automatically cut rough gems. This plugin periodically scans your stocks of rough gems and creates manager orders for cutting them at a Jeweler's Workshop. diff --git a/docs/plugins/autohauler.rst b/docs/plugins/autohauler.rst index 3bdd00950..f28044479 100644 --- a/docs/plugins/autohauler.rst +++ b/docs/plugins/autohauler.rst @@ -1,6 +1,9 @@ autohauler ========== +Tags: +:dfhack-keybind:`autohauler` + Automatically manage hauling labors. Similar to `autolabor`, but instead of managing all labors, ``autohauler`` only addresses hauling labors, leaving the assignment of skilled labors entirely up to you. You can use the in-game @@ -40,7 +43,8 @@ Usage: - ``autohauler debug`` In the next cycle, output the state of every dwarf. -Examples: +Examples +-------- - ``autohauler HAUL_STONE haulers`` Set stone hauling as a hauling labor (this is already the default). diff --git a/docs/plugins/autolabor.rst b/docs/plugins/autolabor.rst index b60146d28..f402703c8 100644 --- a/docs/plugins/autolabor.rst +++ b/docs/plugins/autolabor.rst @@ -1,6 +1,9 @@ autolabor ========= +Tags: +:dfhack-keybind:`autolabor` + Automatically manage dwarf labors. Autolabor attempts to keep as many dwarves as possible busy while allowing dwarves to specialize in specific skills. @@ -49,7 +52,8 @@ dwarfs that meet any of these conditions: We stop assigning dwarves when we reach the maximum allowed. -Advanced usage: +Advanced usage +-------------- - ``autolabor [] []`` Set range of dwarves assigned to a labor, optionally specifying the size of @@ -68,7 +72,8 @@ Advanced usage: See `autolabor-artisans` for a differently-tuned setup. -Examples: +Examples +-------- - ``autolabor MINE 5`` Keep at least 5 dwarves with mining enabled. diff --git a/docs/plugins/automaterial.rst b/docs/plugins/automaterial.rst index 51701a1fc..429a25d70 100644 --- a/docs/plugins/automaterial.rst +++ b/docs/plugins/automaterial.rst @@ -1,6 +1,9 @@ automaterial ============ +Tags: +:dfhack-keybind:`automaterial` + Sorts building materials by recent usage. This makes building constructions (walls, floors, fortifications, etc) much easier by saving you from having to trawl through long lists of materials each time you place one. diff --git a/docs/plugins/automelt.rst b/docs/plugins/automelt.rst index 749aac28a..b16368ac5 100644 --- a/docs/plugins/automelt.rst +++ b/docs/plugins/automelt.rst @@ -1,6 +1,9 @@ automelt ======== +Tags: +:dfhack-keybind:`automelt` + Quickly designate items to be melted. When `enabled `, this plugin adds an option to the :kbd:`q` menu for stockpiles. When the ``automelt`` option is selected for the stockpile, any items placed in the stockpile will automatically diff --git a/docs/plugins/autotrade.rst b/docs/plugins/autotrade.rst index e695a0a53..405621b2b 100644 --- a/docs/plugins/autotrade.rst +++ b/docs/plugins/autotrade.rst @@ -1,6 +1,9 @@ autotrade ========= +Tags: +:dfhack-keybind:`autotrade` + Quickly designate items to be traded. When `enabled `, this plugin adds an option to the :kbd:`q` menu for stockpiles. When the ``autotrade`` option is selected for the stockpile, any items placed in the stockpile will automatically diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst index edfc1f096..9d74fa47a 100644 --- a/docs/plugins/blueprint.rst +++ b/docs/plugins/blueprint.rst @@ -1,6 +1,9 @@ blueprint ========= +Tags: +:dfhack-keybind:`blueprint` + Record a live game map in a quickfort blueprint. With ``blueprint``, you can export the structure of a portion of your fortress in a blueprint file that you (or anyone else) can later play back with `quickfort`. @@ -16,7 +19,8 @@ Usage:: blueprint [] [ []] [] blueprint gui [ []] [] -Examples: +Examples +-------- - ``blueprint gui`` Runs `gui/blueprint`, the interactive frontend, where all configuration for @@ -30,7 +34,8 @@ Examples: the start of the blueprint area is set to a specific value instead of using the in-game cursor position. -Positional Parameters: +Positional parameters +--------------------- - ``width`` Width of the area (in tiles) to translate. @@ -46,7 +51,8 @@ Positional Parameters: some characters other than numbers so the name won't be confused with the optional ``depth`` parameter. -Phases: +Phases +------ If you want to generate blueprints only for specific phases, add their names to the commandline, anywhere after the blueprint base name. You can list multiple @@ -68,7 +74,8 @@ phases; just separate them with a space. If no phases are specified, phases are autodetected. For example, a ``#place`` blueprint will be created only if there are stockpiles in the blueprint area. -Options: +Options +------- - ``-c``, ``--cursor ,,`` Use the specified map coordinates instead of the current cursor position for @@ -98,7 +105,8 @@ Options: "none", which will create a standard quickfort `multi-blueprint ` file. -Output formats: +Output formats +-------------- Here are the values that can be passed to the ``--format`` flag: @@ -109,7 +117,8 @@ Here are the values that can be passed to the ``--format`` flag: Makes the blueprints in the ``.csv`` files easier to read and edit with a text editor by adding extra spacing and alignment markers. -Splitting output into multiple files: +Splitting output into multiple files +------------------------------------ The ``--splitby`` flag can take any of the following values: diff --git a/docs/plugins/building-hacks.rst b/docs/plugins/building-hacks.rst index 950d1751a..78e94fba6 100644 --- a/docs/plugins/building-hacks.rst +++ b/docs/plugins/building-hacks.rst @@ -1,6 +1,9 @@ building-hacks ============== +Tags: +:dfhack-keybind:`building-hacks` + Allows mods to create and manage powered workshops. Usage:: diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index 4b21da8af..62a239e3d 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -1,6 +1,9 @@ buildingplan ============ +Tags: +:dfhack-keybind:`buildingplan` + Plan building construction before you have materials. This plugin adds a planning mode for building placement. You can then place furniture, constructions, and other buildings before the required materials are available, diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst index 658b7d209..f863dd439 100644 --- a/docs/plugins/burrows.rst +++ b/docs/plugins/burrows.rst @@ -1,6 +1,9 @@ burrows ======= +Tags: +:dfhack-keybind:`burrows` + Quick commands for burrow control. Allows manipulating burrows and automated burrow expansion while digging. From c9f69081a64f6b27a7604bd605c20ad2338f5dce Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 20 Jul 2022 07:17:41 +0000 Subject: [PATCH 249/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index ef5fc459c..bd21e9664 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ef5fc459c4f0a65a9fd709c14fed0c5892c9eebd +Subproject commit bd21e9664abf9963d5cf5f0f86222dd0dadda2c5 From 0dd153cc0d499c9b88eda6d81db1a873abcb10af Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 12:34:40 -0700 Subject: [PATCH 250/854] ensure we pick up the plugin help entry even when it has a command and that command is not equal to the plugin name --- library/lua/helpdb.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index a972cca24..f6f9ed864 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -293,25 +293,25 @@ local function scan_plugins(old_db, db) local plugin_names = dfhack.internal.listPlugins() for _,plugin in ipairs(plugin_names) do local commands = dfhack.internal.listCommands(plugin) - if #commands == 0 then - -- use plugin name as the command so we have something to anchor the - -- documentation to - update_db(old_db, db, - has_rendered_help(plugin) and - HELP_SOURCES.RENDERED or HELP_SOURCES.STUB, - plugin, {entry_types={[ENTRY_TYPES.PLUGIN]=true}}) - goto continue - end + local includes_plugin = false for _,command in ipairs(commands) do local entry_types = {[ENTRY_TYPES.COMMAND]=true} if command == plugin then entry_types[ENTRY_TYPES.PLUGIN]=true + includes_plugin = true end update_db(old_db, db, has_rendered_help(command) and HELP_SOURCES.RENDERED or HELP_SOURCES.PLUGIN, command, {entry_types=entry_types}) end + if not includes_plugin then + update_db(old_db, db, + has_rendered_help(plugin) and + HELP_SOURCES.RENDERED or HELP_SOURCES.STUB, + plugin, {entry_types={[ENTRY_TYPES.PLUGIN]=true}}) + goto continue + end ::continue:: end end From 0fe4bed121621815dbd430b66544714723c3acb0 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 12:42:27 -0700 Subject: [PATCH 251/854] document both burrow command and burrows plugin --- docs/plugins/burrow.rst | 49 ++++++++++++++++++++++++++++++++++++++++ docs/plugins/burrows.rst | 48 ++++++++------------------------------- 2 files changed, 58 insertions(+), 39 deletions(-) create mode 100644 docs/plugins/burrow.rst diff --git a/docs/plugins/burrow.rst b/docs/plugins/burrow.rst new file mode 100644 index 000000000..879a7cc78 --- /dev/null +++ b/docs/plugins/burrow.rst @@ -0,0 +1,49 @@ +burrow +====== + +Tags: +:dfhack-keybind:`burrow` + +Quick commands for burrow control. Allows manipulating burrows and automated +burrow expansion while digging. + +Usage: + +- ``burrows enable auto-grow`` + When a wall inside a burrow with a name ending in '+' is dug out, the burrow + will be extended to newly-revealed adjacent walls. This final '+' may be + omitted in burrow name args of other ``burrows`` commands. Note that digging + 1-wide corridors with the miner inside the burrow is SLOW. Be sure to also + run ``enable burrow`` for this feature to work. +- ``burrows disable auto-grow`` + Disables auto-grow processing. +- ``burrows clear-unit [ ...]`` + Remove all units from the named burrows. +- ``burrows clear-tiles [ ...]`` + Remove all tiles from the named burrows. +- ``burrows set-units target-burrow [ ...]`` + Clear all units from the target burrow, then add units from the named source + burrows. +- ``burrows add-units target-burrow [ ...]`` + Add units from the source burrows to the target. +- ``burrows remove-units target-burrow [ ...]`` + Remove units in source burrows from the target. +- ``burrows set-tiles target-burrow [ ...]`` + Clear target burrow tiles and adds tiles from the names source burrows. +- ``burrows add-tiles target-burrow [ ...]`` + Add tiles from the source burrows to the target. +- ``burrows remove-tiles target-burrow [ ...]`` + Remove tiles in source burrows from the target. + +In place of a source burrow, you can use one of the following keywords: + +- ``ABOVE_GROUND`` +- ``SUBTERRANEAN`` +- ``INSIDE`` +- ``OUTSIDE`` +- ``LIGHT`` +- ``DARK`` +- ``HIDDEN`` +- ``REVEALED`` + +to add tiles with the given properties. diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst index f863dd439..9bf375fa8 100644 --- a/docs/plugins/burrows.rst +++ b/docs/plugins/burrows.rst @@ -4,45 +4,15 @@ burrows Tags: :dfhack-keybind:`burrows` -Quick commands for burrow control. Allows manipulating burrows and automated -burrow expansion while digging. +Auto-expand burrows as you dig. When a wall inside a burrow with a name ending +in ``+`` is dug out, the burrow will be extended to newly-revealed adjacent +walls. Note that digging 1-wide corridors with the miner inside the burrow is +SLOW. -Usage: +Usage:: -- ``enable auto-grow`` - When a wall inside a burrow with a name ending in '+' is dug out, the burrow - will be extended to newly-revealed adjacent walls. This final '+' may be - omitted in burrow name args of other ``burrows`` commands. Note that digging - 1-wide corridors with the miner inside the burrow is SLOW. -- ``disable auto-grow`` - Disables auto-grow processing. -- ``clear-unit [ ...]`` - Remove all units from the named burrows. -- ``clear-tiles [ ...]`` - Remove all tiles from the named burrows. -- ``set-units target-burrow [ ...]`` - Clear all units from the target burrow, then add units from the named source - burrows. -- ``add-units target-burrow [ ...]`` - Add units from the source burrows to the target. -- ``remove-units target-burrow [ ...]`` - Remove units in source burrows from the target. -- ``set-tiles target-burrow [ ...]`` - Clear target burrow tiles and adds tiles from the names source burrows. -- ``add-tiles target-burrow [ ...]`` - Add tiles from the source burrows to the target. -- ``remove-tiles target-burrow [ ...]`` - Remove tiles in source burrows from the target. + enable burrows + burrows enable auto-grow -In place of a source burrow, you can use one of the following keywords: - -- ``ABOVE_GROUND`` -- ``SUBTERRANEAN`` -- ``INSIDE`` -- ``OUTSIDE`` -- ``LIGHT`` -- ``DARK`` -- ``HIDDEN`` -- ``REVEALED`` - -to add tiles with the given properties. +Both of the above commands need to be run for the auto-grow functionality to +work. See the `burrow` command for more burrow-related tools. From ddd2e5003aa8b681f6a3210061b1f45851908c11 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 13:00:27 -0700 Subject: [PATCH 252/854] update docs for changeitem --- docs/plugins/changeitem.rst | 61 ++++++++++++++++++++++++------------- plugins/changeitem.cpp | 34 +++------------------ 2 files changed, 43 insertions(+), 52 deletions(-) diff --git a/docs/plugins/changeitem.rst b/docs/plugins/changeitem.rst index c6da2516f..0a841511c 100644 --- a/docs/plugins/changeitem.rst +++ b/docs/plugins/changeitem.rst @@ -1,26 +1,43 @@ changeitem ========== -Allows changing item material and base quality. By default the item currently -selected in the UI will be changed (you can select items in the 'k' list -or inside containers/inventory). By default change is only allowed if materials -is of the same subtype (for example wood<->wood, stone<->stone etc). But since -some transformations work pretty well and may be desired you can override this -with 'force'. Note that some attributes will not be touched, possibly resulting -in weirdness. To get an idea how the RAW id should look like, check some items -with 'info'. Using 'force' might create items which are not touched by -crafters/haulers. - -Options: - -:info: Don't change anything, print some info instead. -:here: Change all items at the cursor position. Requires in-game cursor. -:material, m: Change material. Must be followed by valid material RAW id. -:quality, q: Change base quality. Must be followed by number (0-5). -:force: Ignore subtypes, force change to new material. - -Examples: - -``changeitem m INORGANIC:GRANITE here`` - Change material of all items under the cursor to granite. + +Tags: +:dfhack-keybind: + +Allows changing item material and base quality. By default, a change is only +allowed if the existing and desired item materials are of the same subtype +(for example wood -> wood, stone -> stone, etc). But since some transformations +work pretty well and may be desired you can override this with ``force``. Note +that forced changes can possibly result in items that crafters and haulers +refuse to touch. + +Usage: + +- ``changeitem info`` + Show details about the selected item. Does not change the item. You can use + this command to discover RAW ids for existing items. +- ``changeitem []`` + Change the item selected in the ``k`` list or inside a container/inventory. +- ``changeitem here []`` + Change all items at the cursor position. Requires in-game cursor. + +Options +------- + +- ``m``, ``material `` + Change material. Must be followed by valid material RAW id. +- ``s``, ``subtype `` + Change subtype. Must be followed by a valid subtype RAW id." +- ``q``, ``quality `` + Change base quality. Must be followed by number (0-5) with 0 being no quality + and 5 being masterpiece quality. +- ``force`` + Ignore subtypes and force the change to the new material. + +Examples +-------- + +``changeitem here m INORGANIC:GRANITE`` + Change material of all stone items under the cursor to granite. ``changeitem q 5`` Change currently selected item to masterpiece quality. diff --git a/plugins/changeitem.cpp b/plugins/changeitem.cpp index 52d9e7ab9..49feaec79 100644 --- a/plugins/changeitem.cpp +++ b/plugins/changeitem.cpp @@ -37,37 +37,12 @@ REQUIRE_GLOBAL(world); command_result df_changeitem(color_ostream &out, vector & parameters); -const string changeitem_help = - "Changeitem allows to change some item attributes.\n" - "By default the item currently selected in the UI will be changed\n" - "(you can select items in the 'k' list or inside containers/inventory).\n" - "By default change is only allowed if materials is of the same subtype\n" - "(for example wood<->wood, stone<->stone etc). But since some transformations\n" - "work pretty well and may be desired you can override this with 'force'.\n" - "Note that some attributes will not be touched, possibly resulting in weirdness.\n" - "To get an idea how the RAW id should look like, check some items with 'info'.\n" - "Using 'force' might create items which are not touched by crafters/haulers.\n" - "Options:\n" - " info - don't change anything, print some item info instead\n" - " here - change all items at cursor position\n" - " material, m - change material. must be followed by material RAW id\n" - " subtype, s - change subtype. must be followed by correct RAW id\n" - " quality, q - change base quality. must be followed by number (0-5)\n" - " force - ignore subtypes, force change to new material.\n" - "Example:\n" - " changeitem m INORGANIC:GRANITE here\n" - " change material of all items under the cursor to granite\n" - " changeitem q 5\n" - " change currently selected item to masterpiece quality\n"; - - DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( - "changeitem", "Change item attributes (material, quality).", - df_changeitem, false, - changeitem_help.c_str() - )); + "changeitem", + "Change item attributes (material, quality).", + df_changeitem)); return CR_OK; } @@ -130,8 +105,7 @@ command_result df_changeitem(color_ostream &out, vector & parameters) if (p == "help" || p == "?") { - out << changeitem_help << endl; - return CR_OK; + return CR_WRONG_USAGE; } else if (p == "here") { From fae5f0635a7f6e7523f7d072ba26a244fada0575 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 13:21:05 -0700 Subject: [PATCH 253/854] update docs for changelayer --- docs/plugins/changelayer.rst | 106 +++++++++++++++++++---------------- plugins/changelayer.cpp | 66 ++-------------------- 2 files changed, 63 insertions(+), 109 deletions(-) diff --git a/docs/plugins/changelayer.rst b/docs/plugins/changelayer.rst index 87c27921c..535409e90 100644 --- a/docs/plugins/changelayer.rst +++ b/docs/plugins/changelayer.rst @@ -1,60 +1,72 @@ changelayer =========== -Changes material of the geology layer under cursor to the specified inorganic -RAW material. Can have impact on all surrounding regions, not only your embark! -By default changing stone to soil and vice versa is not allowed. By default -changes only the layer at the cursor position. Note that one layer can stretch -across lots of z levels. By default changes only the geology which is linked -to the biome under the cursor. That geology might be linked to other biomes -as well, though. Mineral veins and gem clusters will stay on the map. Use -`changevein` for them. - -tl;dr: You will end up with changing quite big areas in one go, especially if -you use it in lower z levels. Use with care. - -Options: - -:all_biomes: Change selected layer for all biomes on your map. - Result may be undesirable since the same layer can AND WILL - be on different z-levels for different biomes. Use the tool - 'probe' to get an idea how layers and biomes are distributed - on your map. -:all_layers: Change all layers on your map (only for the selected biome - unless 'all_biomes' is added). - Candy mountain, anyone? Will make your map quite boring, - but tidy. -:force: Allow changing stone to soil and vice versa. !!THIS CAN HAVE - WEIRD EFFECTS, USE WITH CARE!! - Note that soil will not be magically replaced with stone. - You will, however, get a stone floor after digging so it - will allow the floor to be engraved. - Note that stone will not be magically replaced with soil. - You will, however, get a soil floor after digging so it - could be helpful for creating farm plots on maps with no - soil. -:verbose: Give some details about what is being changed. -:trouble: Give some advice about known problems. - -Examples: + +Tags: +:dfhack-keybind: + +Change the material of an entire geology layer. Note that one layer can stretch +across many z-levels, and changes to the geology layer will affect all +surrounding regions, not just your embark! Mineral veins and gem clusters will +not be affected. Use `changevein` if you want to modify those. + +tl;dr: You will end up with changing large areas in one go, especially if you +use it in lower z levels. Use this command with care! + +Usage:: + + changelayer [] + +When run without options, ``changelayer`` will: + +- only affect the geology layer at the current cursor position +- only affect the biome that covers the current cursor position +- not allow changing stone to soil and vice versa + +You can use the `probe` command on various tiles around your map to find valid +material RAW ids and to get an idea how layers and biomes are distributed. + +Options +------- + +- ``all_biomes`` + Change the corresponding geology layer for all biomes on your map. Be aware + that the same geology layer can AND WILL be on different z-levels for + different biomes. +- ``all_layers`` + Change all geology layers on your map (only for the selected biome unless + ``all_biomes`` is also specified). Candy mountain, anyone? Will make your map + quite boring, but tidy. +- ``force`` + Allow changing stone to soil and vice versa. **THIS CAN HAVE WEIRD EFFECTS, + USE WITH CARE AND SAVE FIRST**. Note that soil will not be magically replaced + with stone. You will, however, get a stone floor after digging, so it will + allow the floor to be engraved. Similarly, stone will not be magically + replaced with soil, but you will get a soil floor after digging, so it could + be helpful for creating farm plots on maps with no soil. +- ``verbose`` + Output details about what is being changed. + +Examples +-------- ``changelayer GRANITE`` - Convert layer at cursor position into granite. + Convert the layer at the cursor position into granite. ``changelayer SILTY_CLAY force`` - Convert layer at cursor position into clay even if it's stone. + Convert teh layer at the cursor position into clay, even if it's stone. ``changelayer MARBLE all_biomes all_layers`` Convert all layers of all biomes which are not soil into marble. .. note:: * If you use changelayer and nothing happens, try to pause/unpause the game - for a while and try to move the cursor to another tile. Then try again. - If that doesn't help try temporarily changing some other layer, undo your - changes and try again for the layer you want to change. Saving - and reloading your map might also help. + for a while and move the cursor to another tile. Then try again. If that + doesn't help, then try to temporarily change some other layer, undo your + changes, and try again for the layer you want to change. Saving and + reloading your map also sometimes helps. * You should be fine if you only change single layers without the use - of 'force'. Still it's advisable to save your game before messing with + of 'force'. Still, it's advisable to save your game before messing with the map. - * When you force changelayer to convert soil to stone you might experience - weird stuff (flashing tiles, tiles changed all over place etc). - Try reverting the changes manually or even better use an older savegame. - You did save your game, right? + * When you force changelayer to convert soil to stone, you might see some + weird stuff (flashing tiles, tiles changed all over place etc). Try + reverting the changes manually or even better use an older savegame. You + did save your game, right? diff --git a/plugins/changelayer.cpp b/plugins/changelayer.cpp index 79081c040..347f29b95 100644 --- a/plugins/changelayer.cpp +++ b/plugins/changelayer.cpp @@ -31,66 +31,14 @@ DFHACK_PLUGIN("changelayer"); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(cursor); -const string changelayer_help = - " Allows to change the material of whole geology layers.\n" - " Can have impact on all surrounding regions, not only your embark!\n" - " By default changing stone to soil and vice versa is not allowed.\n" - " By default changes only the layer at the cursor position.\n" - " Note that one layer can stretch across lots of z levels.\n" - " By default changes only the geology which is linked to the biome under the\n" - " cursor. That geology might be linked to other biomes as well, though.\n" - " Mineral veins and gem clusters will stay on the map.\n" - " Use 'changevein' for them.\n\n" - " tl;dr: You will end up with changing quite big areas in one go.\n\n" - "Options (first parameter MUST be the material id):\n" - " all_biomes - Change layer for all biomes on your map.\n" - " Result may be undesirable since the same layer\n" - " can AND WILL be on different z-levels for different biomes.\n" - " Use the tool 'probe' to get an idea how layers and biomes\n" - " are distributed on your map.\n" - " all_layers - Change all layers on your map.\n" - " Candy mountain, anyone?\n" - " Will make your map quite boring, but tidy.\n" - " force - Allow changing stone to soil and vice versa.\n" - " !!THIS CAN HAVE WEIRD EFFECTS, USE WITH CARE!!\n" - " Note that soil will not be magically replaced with stone.\n" - " You will, however, get a stone floor after digging so it\n" - " will allow the floor to be engraved.\n" - " Note that stone will not be magically replaced with soil.\n" - " You will, however, get a soil floor after digging so it\n" - " could be helpful for creating farm plots on maps with no soil.\n" - " verbose - Give some more details about what is being changed.\n" - " trouble - Give some advice for known problems.\n" - "Example:\n" - " changelayer GRANITE\n" - " Convert layer at cursor position into granite.\n" - " changelayer SILTY_CLAY force\n" - " Convert layer at cursor position into clay even if it's stone.\n" - " changelayer MARBLE allbiomes alllayers\n" - " Convert all layers of all biomes into marble.\n"; - -const string changelayer_trouble = - "Known problems with changelayer:\n\n" - " Nothing happens, the material stays the old.\n" - " Pause/unpause the game and/or move the cursor a bit. Then retry.\n" - " Try changing another layer, undo the changes and try again.\n" - " Try saving and loading the game.\n\n" - " Weird stuff happening after using the 'force' option.\n" - " Change former stone layers back to stone, soil back to soil.\n" - " If in doubt, use the 'probe' tool to find tiles with soil walls\n" - " and stone layer type or the other way round.\n"; - - command_result changelayer (color_ostream &out, std::vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "changelayer", "Change a whole geology layer.", - changelayer, false, /* true means that the command can't be used from non-interactive user interface */ - // Extended help string. Used by CR_WRONG_USAGE and the help command: - changelayer_help.c_str() - )); + "changelayer", + "Change a whole geology layer.", + changelayer)); return CR_OK; } @@ -120,13 +68,7 @@ command_result changelayer (color_ostream &out, std::vector & para { if(parameters[i] == "help" || parameters[i] == "?") { - out.print("%s",changelayer_help.c_str()); - return CR_OK; - } - if(parameters[i] == "trouble") - { - out.print("%s",changelayer_trouble.c_str()); - return CR_OK; + return CR_WRONG_USAGE; } if(parameters[i] == "force") force = true; From d3dd12c38f8f7302a288c26c052415f2556c588f Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 13:24:27 -0700 Subject: [PATCH 254/854] update docs for changevein --- docs/plugins/changevein.rst | 25 +++++++++++++++++++------ plugins/changevein.cpp | 3 +-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/plugins/changevein.rst b/docs/plugins/changevein.rst index ad7ab3d54..f4871772e 100644 --- a/docs/plugins/changevein.rst +++ b/docs/plugins/changevein.rst @@ -1,10 +1,23 @@ changevein ========== -Changes material of the vein under cursor to the specified inorganic RAW -material. Only affects tiles within the current 16x16 block - for veins and -large clusters, you will need to use this command multiple times. -Example: +Tags: +:dfhack-keybind: -``changevein NATIVE_PLATINUM`` - Convert vein at cursor position into platinum ore. +Changes the material of a mineral inclusion. You can change it to any incorganic +material RAW id. Note that this command only affects tiles within the current +16x16 block - for large veins and clusters, you will need to use this command +multiple times. + +You can use the `probe` command to discover the material RAW ids for existing +veins that you want to duplicate. + +Usage:: + + changevein + +Example +------- + +- ``changevein NATIVE_PLATINUM`` + Convert vein at cursor position into platinum ore. diff --git a/plugins/changevein.cpp b/plugins/changevein.cpp index 8612603fc..48a1e7325 100644 --- a/plugins/changevein.cpp +++ b/plugins/changevein.cpp @@ -86,8 +86,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector \n")); + df_changevein)); return CR_OK; } From 1f3c4cdd18d9d7f67476d3104ded092161dd92aa Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 13:26:30 -0700 Subject: [PATCH 255/854] update docs for cleanconst --- docs/plugins/cleanconst.rst | 15 +++++++++++---- plugins/cleanconst.cpp | 9 +++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/plugins/cleanconst.rst b/docs/plugins/cleanconst.rst index a8169d83b..c34bac6bf 100644 --- a/docs/plugins/cleanconst.rst +++ b/docs/plugins/cleanconst.rst @@ -1,7 +1,14 @@ cleanconst ========== -Cleans up construction materials. -This utility alters all constructions on the map so that they spawn their -building component when they are disassembled, allowing their actual -build items to be safely deleted. This can improve FPS in extreme situations. +Tags: +:dfhack-keybind: + +Cleans up construction materials. This tool alters all constructions on the map +so that they spawn their building component when they are disassembled, allowing +their actual build items to be safely deleted. This can improve FPS when you +have many constructions on the map. + +Usage:: + + cleanconst diff --git a/plugins/cleanconst.cpp b/plugins/cleanconst.cpp index 84659c5d7..38149abd3 100644 --- a/plugins/cleanconst.cpp +++ b/plugins/cleanconst.cpp @@ -71,12 +71,9 @@ command_result df_cleanconst(color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( - "cleanconst", "Cleans up construction materials.", - df_cleanconst, false, - " This utility alters all constructions on the map so that they spawn their\n" - " building component when they are disassembled, allowing their actual\n" - " build items to be safely deleted.\n" - )); + "cleanconst", + "Cleans up construction materials.", + df_cleanconst)); return CR_OK; } From 048b20ac45c8ff4fde36e32f0ee2c62315efb427 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 13:34:29 -0700 Subject: [PATCH 256/854] no help entries for non-enableable plugins --- library/LuaApi.cpp | 16 ++++++++++++++++ library/lua/helpdb.lua | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index c332933a2..770aab870 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -3147,6 +3147,21 @@ static int internal_getCommandHelp(lua_State *L) return 1; } +static int internal_isPluginEnableable(lua_State *L) +{ + auto plugins = Core::getInstance().getPluginManager(); + + const char *name = luaL_checkstring(L, 1); + + auto plugin = plugins->getPluginByName(name); + if (plugin) + lua_pushboolean(L, plugin->can_be_enabled()); + else + lua_pushnil(L); + + return 1; +} + static int internal_threadid(lua_State *L) { std::stringstream ss; @@ -3221,6 +3236,7 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "listPlugins", internal_listPlugins }, { "listCommands", internal_listCommands }, { "getCommandHelp", internal_getCommandHelp }, + { "isPluginEnableable", internal_isPluginEnableable }, { "threadid", internal_threadid }, { "md5File", internal_md5file }, { NULL, NULL } diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index f6f9ed864..e35d2f125 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -305,7 +305,8 @@ local function scan_plugins(old_db, db) HELP_SOURCES.RENDERED or HELP_SOURCES.PLUGIN, command, {entry_types=entry_types}) end - if not includes_plugin then + if not includes_plugin and + dfhack.internal.isPluginEnableable(plugin) then update_db(old_db, db, has_rendered_help(plugin) and HELP_SOURCES.RENDERED or HELP_SOURCES.STUB, From e9e477c680948ea016e8c4dffe61c10ae6168ffe Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 13:51:03 -0700 Subject: [PATCH 257/854] update docs for clean and spotclean --- docs/plugins/clean.rst | 41 ++++++++++++++++++++++++++++---------- docs/plugins/spotclean.rst | 14 ++++++++++--- plugins/cleaners.cpp | 26 ++++++------------------ 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/docs/plugins/clean.rst b/docs/plugins/clean.rst index f2d0c361d..54421ce91 100644 --- a/docs/plugins/clean.rst +++ b/docs/plugins/clean.rst @@ -1,16 +1,37 @@ clean ===== -Cleans all the splatter that get scattered all over the map, items and -creatures. In an old fortress, this can significantly reduce FPS lag. It can -also spoil your !!FUN!!, so think before you use it. -Options: +Tags: +:dfhack-keybind: -:map: Clean the map tiles. By default, it leaves mud and snow alone. -:units: Clean the creatures. Will also clean hostiles. -:items: Clean all the items. Even a poisoned blade. +Removes contaminants from tiles, items, and units. More specifically, it +cleans all the splatter that get scattered all over the map and that clings to +your items and units. In an old fortress, this can significantly reduce FPS lag. +It can also spoil your !!FUN!!, so think before you use it. -Extra options for ``map``: +Usage:: -:mud: Remove mud in addition to the normal stuff. -:snow: Also remove snow coverings. + clean all|map|items|units|plants [] + +By default, cleaning the map leaves mud and snow alone. Note that cleaning units +includes hostiles, and that cleaning items removes poisons from weapons. + +Options +------- + +When cleaning the map, you can specify extra options for extra cleaning: + +- ``mud`` + Also remove mud. +- ``item`` + Also remove item spatter, like fallen leaves and flowers. +- ``snow`` + Also remove snow coverings. + +Examples +-------- + +- ``clean all`` + Clean everything that can be cleaned (except mud and snow). +- ``clean all mud item snow`` + Removes all spatter, including mud, leaves, and snow from map tiles. diff --git a/docs/plugins/spotclean.rst b/docs/plugins/spotclean.rst index 9ccdd2cc6..dcad047a4 100644 --- a/docs/plugins/spotclean.rst +++ b/docs/plugins/spotclean.rst @@ -1,6 +1,14 @@ spotclean ========= -Works like ``clean map snow mud``, but only for the tile under the cursor. Ideal -if you want to keep that bloody entrance ``clean map`` would clean up. -:dfhack-keybind:`spotclean` +Tags: +:dfhack-keybind: + +Cleans a map tile of contaminants and spatter. It works like +``clean map snow mud``, but only for the tile under the cursor. Ideal if you +just want to clean a specific tile but don't want the `clean` command to remove +all the glorious blood from your entranceway. + +Usage:: + + spotclean diff --git a/plugins/cleaners.cpp b/plugins/cleaners.cpp index 24694c6a2..7389488f1 100644 --- a/plugins/cleaners.cpp +++ b/plugins/cleaners.cpp @@ -241,27 +241,13 @@ command_result clean (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "clean","Remove contaminants from tiles, items and creatures.", - clean, false, - " Removes contaminants from map tiles, items and creatures.\n" - "Options:\n" - " map - clean the map tiles\n" - " items - clean all items\n" - " units - clean all creatures\n" - " plants - clean all plants\n" - " all - clean everything.\n" - "More options for 'map':\n" - " snow - also remove snow\n" - " mud - also remove mud\n" - " item - also remove item spatters (e.g. leaves and flowers)\n" - "Example:\n" - " clean all mud snow item\n" - " Removes all spatter, including mud and snow from map tiles.\n" - )); + "clean", + "Remove contaminants from tiles, items and creatures.", + clean)); commands.push_back(PluginCommand( - "spotclean","Cleans map tile under cursor.", - spotclean,Gui::cursor_hotkey - )); + "spotclean", + "Cleans map tile under cursor.", + spotclean,Gui::cursor_hotkey)); return CR_OK; } From d9a1104473f8aa9b3eaf264cd21bfbeddd50943c Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 14:51:06 -0700 Subject: [PATCH 258/854] update docs for cleanowned --- docs/plugins/cleanowned.rst | 42 ++++++++++++++++++++++++++----------- plugins/cleanowned.cpp | 19 +++-------------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/docs/plugins/cleanowned.rst b/docs/plugins/cleanowned.rst index 7f80e35cf..27e4806a2 100644 --- a/docs/plugins/cleanowned.rst +++ b/docs/plugins/cleanowned.rst @@ -1,19 +1,37 @@ cleanowned ========== -Confiscates items owned by dwarfs. By default, owned food on the floor -and rotten items are confistacted and dumped. -Options: +Tags: +:dfhack-keybind: -:all: confiscate all owned items -:scattered: confiscated and dump all items scattered on the floor -:x: confiscate/dump items with wear level 'x' and more -:X: confiscate/dump items with wear level 'X' and more -:dryrun: a dry run. combine with other options to see what will happen - without it actually happening. +Confiscates and dumps garbage owned by dwarves. This tool gets dwarves to give +up ownership of scattered items and items with heavy wear and then marks those +items for dumping. Now you can finally get your dwarves to give up their rotten +food and tattered loincloths and go get new ones! -Example: +Usage:: -``cleanowned scattered X`` - This will confiscate rotten and dropped food, garbage on the floors and any + cleanowned [] [dryrun] + +When run without parameters, ``cleanowned`` will confiscate and dump rotten +items and owned food that is left behind on the floor. Specify the ``dryrun`` +parameter to just print out what would be done, but don't actually confiscate +anything. + +You can confiscate additional types of items by adding them to the commandline: + +- ``scattered`` + Confiscate/dump all items scattered on the floor. +- ``x`` + Confiscate/dump items with wear level 'x' (lightly worn) and more. +- ``X`` + Confiscate/dump items with wear level 'X' (heavily worn) and more. + +Or you can confiscate all owned items by specifying ``all``. + +Example +------- + +- ``cleanowned scattered X`` + Confiscate and dump rotten and dropped food, garbage on the floors, and any worn items with 'X' damage and above. diff --git a/plugins/cleanowned.cpp b/plugins/cleanowned.cpp index 90b0e743d..c1e5b31d4 100644 --- a/plugins/cleanowned.cpp +++ b/plugins/cleanowned.cpp @@ -31,22 +31,9 @@ command_result df_cleanowned (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "cleanowned", "Confiscates and dumps garbage owned by dwarfs.", - df_cleanowned, false, - " This tool lets you confiscate and dump all the garbage\n" - " dwarves ultimately accumulate.\n" - " By default, only rotten and dropped food is confiscated.\n" - "Options:\n" - " dryrun - don't actually do anything, just print what would be done.\n" - " scattered - confiscate owned items on the ground\n" - " all - confiscate everything\n" - " x - confiscate & dump 'x' and worse damaged items\n" - " X - confiscate & dump 'X' and worse damaged items\n" - "Example:\n" - " cleanowned scattered X\n" - " This will confiscate rotten and dropped food, garbage on the floors\n" - " and any worn items with 'X' damage and above.\n" - )); + "cleanowned", + "Confiscates and dumps garbage owned by dwarves.", + df_cleanowned)); return CR_OK; } From 9dcb63da53a3dd1b641e1a6da9b65dac53a49b5c Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 15:36:17 -0700 Subject: [PATCH 259/854] don't bork on no frame, set cursor to end of text --- library/lua/gui/widgets.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index aea62bf87..5765992af 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -121,12 +121,12 @@ ResizingPanel = defclass(ResizingPanel, Panel) -- adjust our frame dimensions according to positions and sizes of our subviews function ResizingPanel:postUpdateLayout(frame_body) local w, h = 0, 0 - for _,subview in ipairs(self.subviews) do - if subview.visible then - w = math.max(w, (subview.frame.l or 0) + - (subview.frame.w or frame_body.width)) - h = math.max(h, (subview.frame.t or 0) + - (subview.frame.h or frame_body.height)) + for _,s in ipairs(self.subviews) do + if s.visible then + w = math.max(w, (s.frame and s.frame.l or 0) + + (s.frame and s.frame.w or frame_body.width)) + h = math.max(h, (s.frame and s.frame.t or 0) + + (s.frame and s.frame.h or frame_body.height)) end end if not self.frame then self.frame = {} end @@ -191,8 +191,8 @@ EditField.ATTRS{ } function EditField:preinit(init_table) - local frame = init_table.frame or {} - frame.h = frame.h or 1 + init_table.frame = init_table.frame or {} + init_table.frame.h = init_table.frame.h or 1 end function EditField:init() @@ -202,7 +202,7 @@ function EditField:init() end self.start_pos = 1 - self.cursor = 1 + self.cursor = #self.text + 1 self:addviews{HotkeyLabel{frame={t=0,l=0}, key=self.key, From 64b793b409fedb38e42367ac9b6ec1543e84d18e Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 15:40:49 -0700 Subject: [PATCH 260/854] support EditField:setText() so scripts can use it and be compatible with both the develop and docs branch --- library/lua/gui/widgets.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 1a1b4c6fb..ddd5a01d0 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -207,6 +207,10 @@ function EditField:getPreferredFocusState() return not self.key end +function EditField:setText(text, cursor) + self.text = text +end + function EditField:postUpdateLayout() self.text_offset = self.subviews[1]:getTextWidth() end From 837215ea647b4decdfdb3f320088974d5a89a048 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 16:28:11 -0700 Subject: [PATCH 261/854] modify ci/script-docs.py to read new doc locations --- ci/script-docs.py | 69 +++++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/ci/script-docs.py b/ci/script-docs.py index 7ef287f8a..0106a8f70 100755 --- a/ci/script-docs.py +++ b/ci/script-docs.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 import os -from os.path import basename, dirname, join, splitext +from os.path import basename, dirname, exists, join, splitext import sys SCRIPT_PATH = sys.argv[1] if len(sys.argv) > 1 else 'scripts' +DOCS_PATH = join(SCRIPT_PATH, 'docs') IS_GITHUB_ACTIONS = bool(os.environ.get('GITHUB_ACTIONS')) -def expected_cmd(path): +def get_cmd(path): """Get the command from the name of a script.""" dname, fname = basename(dirname(path)), splitext(basename(path))[0] if dname in ('devel', 'fix', 'gui', 'modtools'): @@ -14,16 +15,6 @@ def expected_cmd(path): return fname -def check_ls(fname, line): - """Check length & existence of leading comment for "ls" builtin command.""" - line = line.strip() - comment = '--' if fname.endswith('.lua') else '#' - if '[====[' in line or not line.startswith(comment): - print_error('missing leading comment (requred for `ls`)', fname) - return 1 - return 0 - - def print_error(message, filename, line=None): if not isinstance(line, int): line = 1 @@ -32,48 +23,42 @@ def print_error(message, filename, line=None): print('::error file=%s,line=%i::%s' % (filename, line, message)) +def check_ls(docfile, lines): + """Check length & existence of first sentence for "ls" builtin command.""" + # TODO + return 0 + + def check_file(fname): - errors, doclines = 0, [] - tok1, tok2 = ('=begin', '=end') if fname.endswith('.rb') else \ - ('[====[', ']====]') - doc_start_line = None - with open(fname, errors='ignore') as f: + errors, doc_start_line = 0, None + docfile = join(DOCS_PATH, get_cmd(fname)+'.rst') + if not exists(docfile): + print_error('missing documentation file: {!r}'.format(docfile), fname) + return 1 + with open(docfile, errors='ignore') as f: lines = f.readlines() if not lines: - print_error('empty file', fname) + print_error('empty documentation file', docfile) return 1 - errors += check_ls(fname, lines[0]) for i, l in enumerate(lines): - if doclines or l.strip().endswith(tok1): - if not doclines: - doc_start_line = i + 1 - doclines.append(l.rstrip()) - if l.startswith(tok2): - break - else: - if doclines: - print_error('docs start but do not end', fname, doc_start_line) - else: - print_error('no documentation found', fname) - return 1 - - if not doclines: - print_error('missing or malformed documentation', fname) - return 1 + l = l.strip() + if l and not doc_start_line and doc_start_line != 0: + doc_start_line = i + doc_end_line = i + lines[i] = l - title, underline = [d for d in doclines - if d and '=begin' not in d and '[====[' not in d][:2] - title_line = doc_start_line + doclines.index(title) + errors += check_ls(docfile, lines) + title, underline = lines[doc_start_line:doc_start_line+2] expected_underline = '=' * len(title) if underline != expected_underline: print_error('title/underline mismatch: expected {!r}, got {!r}'.format( expected_underline, underline), - fname, title_line + 1) + docfile, doc_start_line+1) errors += 1 - if title != expected_cmd(fname): + if title != get_cmd(fname): print_error('expected script title {!r}, got {!r}'.format( - expected_cmd(fname), title), - fname, title_line) + get_cmd(fname), title), + docfile, doc_start_line) errors += 1 return errors From 2f50d161d932d9aa9c8bc8c017869e8eebd8ee1a Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 20 Jul 2022 23:31:20 +0000 Subject: [PATCH 262/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 1595cc1fe..a04727cc4 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 1595cc1fe1f4662eebffdb4a77355491aafacea6 +Subproject commit a04727cc4ec350399ce4d2ded4425f33c50cea50 diff --git a/scripts b/scripts index bd21e9664..6f5c7edc4 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit bd21e9664abf9963d5cf5f0f86222dd0dadda2c5 +Subproject commit 6f5c7edc49beec38c41ac8c3f0b5bded519ab5ce From 738611383b3a465ee420742e0d44faed1fbc9ae0 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 20 Jul 2022 23:33:08 +0000 Subject: [PATCH 263/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 6f5c7edc4..dbdfacd33 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 6f5c7edc49beec38c41ac8c3f0b5bded519ab5ce +Subproject commit dbdfacd33a213f081fbb1d6c85c11424ce3df234 From 6e89f89b3f1600156c5638d9cc9755b11ec42eb5 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 20 Jul 2022 23:38:25 +0000 Subject: [PATCH 264/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index dbdfacd33..78b7554a1 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit dbdfacd33a213f081fbb1d6c85c11424ce3df234 +Subproject commit 78b7554a17d7d385986b2f801d5791485e740afb From 68b8837a8d8b37767a604d136b622ac51733aa09 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 22 Jul 2022 00:30:36 -0400 Subject: [PATCH 265/854] Replace docs/build*.sh with more flexible build.py Notable changes: - can build any combination of output formats in series - `-E` is no longer passed by default to aid in development, but can be passed manually --- docs/build-pdf.sh | 4 +-- docs/build.py | 90 +++++++++++++++++++++++++++++++++++++++++++++++ docs/build.sh | 5 +-- 3 files changed, 92 insertions(+), 7 deletions(-) create mode 100755 docs/build.py diff --git a/docs/build-pdf.sh b/docs/build-pdf.sh index 735ef2faa..a7527cb34 100755 --- a/docs/build-pdf.sh +++ b/docs/build-pdf.sh @@ -9,6 +9,4 @@ # https://www.sphinx-doc.org/en/master/man/sphinx-build.html cd $(dirname "$0") -cd .. - -"${SPHINX:-sphinx-build}" -M latexpdf -d build/docs/pdf . docs/pdf -w build/docs/pdf/_sphinx-warnings.txt -j "${JOBS:-auto}" "$@" +python3 build.py pdf "$@" diff --git a/docs/build.py b/docs/build.py new file mode 100755 index 000000000..2a42d0968 --- /dev/null +++ b/docs/build.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +# for help, run: python3 build.py --help + +import argparse +import os +import subprocess +import sys + +class SphinxOutputFormat: + def __init__(self, name, pre_args): + self.name = str(name) + self.pre_args = tuple(pre_args) + + @property + def args(self): + output_dir = os.path.join('docs', self.name) + artifacts_dir = os.path.join('build', 'docs', self.name) # for artifacts not part of the final documentation + return [ + *self.pre_args, + '.', # source dir + output_dir, + '-d', artifacts_dir, + '-w', os.path.join(artifacts_dir, 'sphinx-warnings.txt'), + ] + +OUTPUT_FORMATS = { + 'html': SphinxOutputFormat('html', pre_args=['-b', 'html']), + 'text': SphinxOutputFormat('text', pre_args=['-b', 'text']), + 'pdf': SphinxOutputFormat('pdf', pre_args=['-M', 'latexpdf']), +} + +def _parse_known_args(parser, source_args): + # pass along any arguments after '--' + ignored_args = [] + if '--' in source_args: + source_args, ignored_args = source_args[:source_args.index('--')], source_args[source_args.index('--')+1:] + args, forward_args = parser.parse_known_args(source_args) + forward_args += ignored_args + return args, forward_args + +def parse_args(source_args): + def output_format(s): + if s in OUTPUT_FORMATS: + return s + raise ValueError + + parser = argparse.ArgumentParser(usage='%(prog)s [{} ...] [options] [--] [sphinx_options]'.format('|'.join(OUTPUT_FORMATS.keys())), description=''' + DFHack wrapper around sphinx-build. + + Any unrecognized options are passed directly to sphinx-build, as well as any + options following a '--' argument, if specified. + ''') + parser.add_argument('format', nargs='*', type=output_format, action='append', + help='Documentation format(s) to build - choose from {}'.format(', '.join(OUTPUT_FORMATS.keys()))) + parser.add_argument('-E', '--clean', action='store_true', + help='Re-read all input files') + parser.add_argument('--sphinx', type=str, default=os.environ.get('SPHINX', 'sphinx-build'), + help='Sphinx executable to run [environment variable: SPHINX; default: "sphinx-build"]') + parser.add_argument('-j', '--jobs', type=str, default=os.environ.get('JOBS', 'auto'), + help='Number of Sphinx threads to run [environment variable: JOBS; default: "auto"]') + parser.add_argument('--debug', action='store_true', + help='Log commands that are run, etc.') + args, forward_args = _parse_known_args(parser, source_args) + + # work around weirdness with list args + args.format = args.format[0] + if not args.format: + args.format = ['html'] + + return args, forward_args + +if __name__ == '__main__': + os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + if not os.path.isfile('conf.py'): + print('Could not find conf.py', file=sys.stderr) + exit(1) + + args, forward_args = parse_args(sys.argv[1:]) + for format_name in args.format: + command = [args.sphinx] + OUTPUT_FORMATS[format_name].args + ['-j', args.jobs] + if args.clean: + command += ['-E'] + command += forward_args + + if args.debug: + print('Building:', format_name) + print('Running:', command) + subprocess.call(command) + print('') diff --git a/docs/build.sh b/docs/build.sh index 28182d9c0..7a6bd0b33 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -9,7 +9,4 @@ # https://www.sphinx-doc.org/en/master/man/sphinx-build.html cd $(dirname "$0") -cd .. - -"${SPHINX:-sphinx-build}" -E -b html -d build/docs/html . docs/html -w build/docs/html/_sphinx-warnings.txt -j "${JOBS:-auto}" "$@" -"${SPHINX:-sphinx-build}" -E -b text -d build/docs/text . docs/text -w build/docs/text/_sphinx-warnings.txt -j "${JOBS:-auto}" "$@" +python3 build.py html text "$@" From 4b0b0e02f87956e42ed19de72b93c00853196b0a Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 22 Jul 2022 00:46:44 -0400 Subject: [PATCH 266/854] Update Documentation.rst for new workflow and output formats --- docs/Documentation.rst | 48 ++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/docs/Documentation.rst b/docs/Documentation.rst index acfffdbbb..c035a0366 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -261,30 +261,45 @@ ways to do this: it to add the flag. You can also run ``cmake`` on the command line, similar to other platforms. -The generated documentation will be stored in ``docs/html`` in the root DFHack -folder, and will be installed to ``hack/docs`` when you next install DFHack in a -DF folder. +By default, both HTML and text docs are built by CMake. The generated +documentation is stored in ``docs/html`` and ``docs/text`` (respectively) in the +root DFHack folder, and will be installed to ``hack/docs`` when you install +DFHack. Running Sphinx manually ----------------------- You can also build the documentation without running CMake - this is faster if -you only want to rebuild the documentation regardless of any code changes. There -is a ``docs/build.sh`` script provided for Linux and macOS that will run -essentially the same command that CMake runs when building the docs - see the -script for additional options. +you only want to rebuild the documentation regardless of any code changes. The +``docs/build.py`` script will build the documentation in any specified formats +(HTML only by default) using essentially the same command that CMake runs when +building the docs. Run the script with ``--help`` to see additional options. -To build the documentation with default options, run the following command from -the root DFHack folder:: +Examples: + +* ``docs/build.py`` + Build just the HTML docs + +* ``docs/build.py html text`` + Build both the HTML and text docs + +* ``docs/build.py --clean`` + Build HTML and force a clean build (all source files are re-read) + +The resulting documentation will be stored in ``docs/html`` and/or ``docs/text``. + +Alternatively, you can run Sphinx manually with:: sphinx-build . docs/html -The resulting documentation will be stored in ``docs/html`` (you can specify -a different path when running ``sphinx-build`` manually, but be warned that -Sphinx may overwrite existing files in this folder). +or, to build plain-text output:: + + sphinx-build -b text . docs/text Sphinx has many options to enable clean builds, parallel builds, logging, and -more - run ``sphinx-build --help`` for details. +more - run ``sphinx-build --help`` for details. If you specify a different +output path, be warned that Sphinx may overwrite existing files in the output +folder. Building a PDF version ---------------------- @@ -295,10 +310,11 @@ want to build a PDF version locally, you will need ``pdflatex``, which is part of a TeX distribution. The following command will then build a PDF, located in ``docs/pdf/latex/DFHack.pdf``, with default options:: - sphinx-build -M latexpdf . docs/pdf + docs/build.py pdf -There is a ``docs/build-pdf.sh`` script provided for Linux and macOS that runs -this command for convenience - see the script for additional options. +Alternatively, you can run Sphinx manually with:: + + sphinx-build -M latexpdf . docs/pdf .. _build-changelog: From 5521a5a45d29c60bd39ee7231dfebc93946d9932 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 22 Jul 2022 00:47:33 -0400 Subject: [PATCH 267/854] Remove old build*.sh scripts --- docs/build-pdf.sh | 12 ------------ docs/build.sh | 12 ------------ 2 files changed, 24 deletions(-) delete mode 100755 docs/build-pdf.sh delete mode 100755 docs/build.sh diff --git a/docs/build-pdf.sh b/docs/build-pdf.sh deleted file mode 100755 index a7527cb34..000000000 --- a/docs/build-pdf.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -# usage: -# ./build-pdf.sh -# SPHINX=/path/to/sphinx-build ./build-pdf.sh -# JOBS=3 ./build-pdf.sh ... -# all command-line arguments are passed directly to sphinx-build - run -# ``sphinx-build --help`` for a list, or see -# https://www.sphinx-doc.org/en/master/man/sphinx-build.html - -cd $(dirname "$0") -python3 build.py pdf "$@" diff --git a/docs/build.sh b/docs/build.sh deleted file mode 100755 index 7a6bd0b33..000000000 --- a/docs/build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -# usage: -# ./build.sh -# SPHINX=/path/to/sphinx-build ./build.sh -# JOBS=3 ./build.sh ... -# all command-line arguments are passed directly to sphinx-build - run -# ``sphinx-build --help`` for a list, or see -# https://www.sphinx-doc.org/en/master/man/sphinx-build.html - -cd $(dirname "$0") -python3 build.py html text "$@" From 2ce7518562b31116feb9bd0cb925838e07f25d40 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 21 Jul 2022 22:33:43 -0700 Subject: [PATCH 268/854] read plugin command docs from single plugin file --- library/LuaApi.cpp | 65 ++++++---- library/lua/helpdb.lua | 273 +++++++++++++++++++++++------------------ 2 files changed, 196 insertions(+), 142 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 770aab870..4ee1a4d1c 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -3109,41 +3109,58 @@ static int internal_listCommands(lua_State *L) } return 1; } -static int internal_getCommandHelp(lua_State *L) + +static const PluginCommand * getPluginCommand(const char * command) { auto plugins = Core::getInstance().getPluginManager(); - - const char *name = luaL_checkstring(L, 1); - - auto plugin = plugins->getPluginByCommand(name); + auto plugin = plugins->getPluginByCommand(command); if (!plugin) { - lua_pushnil(L); - return 1; + return NULL; } size_t num_commands = plugin->size(); for (size_t i = 0; i < num_commands; ++i) { - if ((*plugin)[i].name == name) - { - const auto &pc = (*plugin)[i]; - std::string help = pc.description; - if (help.size() && help[help.size()-1] != '.') - { - help += "."; - } - if (pc.usage.size()) - { - help += "\n" + pc.usage; - } - lua_pushstring(L, help.c_str()); - return 1; - } + if ((*plugin)[i].name == command) + return &(*plugin)[i]; } // not found (somehow) - lua_pushnil(L); + return NULL; +} + +static int internal_getCommandHelp(lua_State *L) +{ + const PluginCommand *pc = getPluginCommand(luaL_checkstring(L, 1)); + if (!pc) + { + lua_pushnil(L); + return 1; + } + + std::string help = pc->description; + if (help.size() && help[help.size()-1] != '.') + help += "."; + if (pc->usage.size()) + help += "\n" + pc->usage; + lua_pushstring(L, help.c_str()); + return 1; +} + +static int internal_getCommandDescription(lua_State *L) +{ + const PluginCommand *pc = getPluginCommand(luaL_checkstring(L, 1)); + if (!pc) + { + lua_pushnil(L); + return 1; + } + + std::string help = pc->description; + if (help.size() && help[help.size()-1] != '.') + help += "."; + lua_pushstring(L, help.c_str()); return 1; } @@ -3218,6 +3235,7 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "getAddress", internal_getAddress }, { "setAddress", internal_setAddress }, { "getVTable", internal_getVTable }, + { "adjustOffset", internal_adjustOffset }, { "getMemRanges", internal_getMemRanges }, { "patchMemory", internal_patchMemory }, @@ -3236,6 +3254,7 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "listPlugins", internal_listPlugins }, { "listCommands", internal_listCommands }, { "getCommandHelp", internal_getCommandHelp }, + { "getCommandDescription", internal_getCommandDescription }, { "isPluginEnableable", internal_isPluginEnableable }, { "threadid", internal_threadid }, { "md5File", internal_md5file }, diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index e35d2f125..647d08996 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -32,58 +32,75 @@ local ENTRY_TYPES = { } local HELP_SOURCES = { - STUB='stub', - RENDERED='rendered', - PLUGIN='plugin', - SCRIPT='script', + RENDERED='rendered', -- from the installed, rendered help text + PLUGIN='plugin', -- from the plugin source code + SCRIPT='script', -- from the script source code + STUB='stub', -- from a generated stub } --- builtins +-- builtin command names, with aliases mapped to their canonical form local BUILTINS = { - 'alias', - 'clear', - 'cls', - 'devel/dump-rpc', - 'die', - 'dir', - 'disable', - 'enable', - 'fpause', - 'help', - 'hide', - 'keybinding', - 'kill-lua', - 'load', - 'ls', - 'man', - 'plug', - 'reload', - 'script', - 'sc-script', - 'show', - 'tags', - 'type', - 'unload', + alias=true, + clear='cls', + cls=true, + ['devel/dump-rpc']=true, + die=true, + dir='ls', + disable=true, + enable=true, + fpause=true, + help=true, + hide=true, + keybinding=true, + ['kill-lua']=true, + ['load']=true, + ls=true, + man='help', + plug=true, + reload=true, + script=true, + ['sc-script']=true, + show=true, + tags=true, + ['type']=true, + unload=true, } +--------------------------------------------------------------------------- +-- data structures +--------------------------------------------------------------------------- + +-- help text database, keys are a subset of the entry database -- entry name -> { --- entry_types (set of ENTRY_TYPES), +-- help_source (element of HELP_SOURCES), -- short_help (string), -- long_help (string), -- tags (set), --- help_source (element of HELP_SOURCES), -- source_timestamp (mtime, 0 for non-files), -- source_path (string, nil for non-files) -- } +textdb = textdb or {} + +-- entry database, points to text in textdb +-- entry name -> { +-- entry_types (set of ENTRY_TYPES), +-- short_help (string, if not nil then overrides short_help in text_entry), +-- text_entry (string) +-- } -- -- entry_types is a set because plugin commands can also be the plugin names. -db = db or {} +entrydb = entrydb or {} + -- tag name -> list of entry names -- Tags defined in the TAG_DEFINITIONS file that have no associated db entries -- will have an empty list. tag_index = tag_index or {} +--------------------------------------------------------------------------- +-- data ingestion +--------------------------------------------------------------------------- + local function get_rendered_path(entry_name) return RENDERED_PATH .. entry_name .. '.txt' end @@ -98,18 +115,16 @@ local DEFAULT_HELP_TEMPLATE = [[ No help available. ]] -local function make_default_entry(entry_name, entry_types, source, - source_timestamp, source_path) +local function make_default_entry(entry_name, help_source, kwargs) local default_long_help = DEFAULT_HELP_TEMPLATE:format( entry_name, ('*'):rep(#entry_name)) return { - entry_types=entry_types, + help_source=help_source, short_help='No help available.', long_help=default_long_help, tags={}, - help_source=source, - source_timestamp=source_timestamp or 0, - source_path=source_path} + source_timestamp=kwargs.source_timestamp or 0, + source_path=kwargs.source_path} end -- updates the short_text, the long_text, and the tags in the given entry based @@ -129,7 +144,8 @@ local function update_entry(entry, iterator, opts) local lines = {} local first_line_is_short_help = opts.first_line_is_short_help local begin_marker_found,header_found = not opts.begin_marker,opts.no_header - local tags_found, short_help_found, in_short_help = false, false, false + local tags_found, short_help_found = false, opts.skip_short_help + local in_short_help = false for line in iterator do if not short_help_found and first_line_is_short_help then line = line:trim() @@ -193,31 +209,30 @@ local function update_entry(entry, iterator, opts) end -- create db entry based on parsing sphinx-rendered help text -local function make_rendered_entry(old_entry, entry_name, entry_types) - local rendered_path = get_rendered_path(entry_name) - local source_timestamp = dfhack.filesystem.mtime(rendered_path) - if old_entry and old_entry.source == HELP_SOURCES.RENDERED and +local function make_rendered_entry(old_entry, entry_name, kwargs) + local source_path = get_rendered_path(entry_name) + local source_timestamp = dfhack.filesystem.mtime(source_path) + if old_entry and old_entry.help_source == HELP_SOURCES.RENDERED and old_entry.source_timestamp >= source_timestamp then -- we already have the latest info return old_entry end - local entry = make_default_entry(entry_name, entry_types, - HELP_SOURCES.RENDERED, source_timestamp, rendered_path) - update_entry(entry, io.lines(rendered_path)) + kwargs.source_path, kwargs.source_timestamp = source_path, source_timestamp + local entry = make_default_entry(entry_name, HELP_SOURCES.RENDERED, kwargs) + update_entry(entry, io.lines(source_path)) return entry end -- create db entry based on the help text in the plugin source (used by -- out-of-tree plugins) -local function make_plugin_entry(old_entry, entry_name, entry_types) +local function make_plugin_entry(old_entry, entry_name, kwargs) if old_entry and old_entry.source == HELP_SOURCES.PLUGIN then -- we can't tell when a plugin is reloaded, so we can either choose to -- always refresh or never refresh. let's go with never for now for -- performance. return old_entry end - local entry = make_default_entry(entry_name, entry_types, - HELP_SOURCES.PLUGIN) + local entry = make_default_entry(entry_name, HELP_SOURCES.PLUGIN, kwargs) local long_help = dfhack.internal.getCommandHelp(entry_name) if long_help and #long_help:trim() > 0 then update_entry(entry, long_help:trim():gmatch('[^\n]*'), {no_header=true}) @@ -227,19 +242,22 @@ end -- create db entry based on the help text in the script source (used by -- out-of-tree scripts) -local function make_script_entry(old_entry, entry_name, script_source_path) - local source_timestamp = dfhack.filesystem.mtime(script_source_path) +local function make_script_entry(old_entry, entry_name, kwargs) + local source_path = kwargs.source_path + local source_timestamp = dfhack.filesystem.mtime(source_path) if old_entry and old_entry.source == HELP_SOURCES.SCRIPT and - old_entry.script_source_path == script_source_path and + old_entry.source_path == source_path and old_entry.source_timestamp >= source_timestamp then -- we already have the latest info return old_entry end - local entry = make_default_entry(entry_name, {[ENTRY_TYPES.COMMAND]=true}, - HELP_SOURCES.SCRIPT, source_timestamp, script_source_path) - local ok, lines = pcall(io.lines, script_source_path) - if not ok then return entry end - local is_rb = script_source_path:endswith('.rb') + kwargs.source_timestamp, kwargs.entry_type = source_timestamp + local entry = make_default_entry(entry_name, HELP_SOURCES.SCRIPT, kwargs) + local ok, lines = pcall(io.lines, source_path) + if not ok then + return entry + end + local is_rb = source_path:endswith('.rb') update_entry(entry, lines, {begin_marker=(is_rb and SCRIPT_DOC_BEGIN_RUBY or SCRIPT_DOC_BEGIN), end_marker=(is_rb and SCRIPT_DOC_BEGIN_RUBY or SCRIPT_DOC_END), @@ -247,78 +265,81 @@ local function make_script_entry(old_entry, entry_name, script_source_path) return entry end --- updates the db (and associated tag index) with a new entry if the entry_name --- doesn't already exist in the db. -local function update_db(old_db, db, source, entry_name, kwargs) - if db[entry_name] then +-- updates the dbs (and associated tag index) with a new entry if the entry_name +-- doesn't already exist in the dbs. +local function update_db(old_db, entry_name, text_entry, help_source, kwargs) + if entrydb[entry_name] then -- already in db (e.g. from a higher-priority script dir); skip return end - local entry, old_entry = nil, old_db[entry_name] - if source == HELP_SOURCES.RENDERED then - entry = make_rendered_entry(old_entry, entry_name, kwargs.entry_types) - elseif source == HELP_SOURCES.PLUGIN then - entry = make_plugin_entry(old_entry, entry_name, kwargs.entry_types) - elseif source == HELP_SOURCES.SCRIPT then - entry = make_script_entry(old_entry, entry_name, kwargs.script_source) - elseif source == HELP_SOURCES.STUB then - entry = make_default_entry(entry_name, kwargs.entry_types, - HELP_SOURCES.STUB) - else - error('unhandled help source: ' .. source) + entrydb[entry_name] = { + entry_types=kwargs.entry_types, + short_help=kwargs.short_help, + text_entry=text_entry + } + if entry_name ~= text_entry then + return end - db[entry_name] = entry - for tag in pairs(entry.tags) do - -- ignore unknown tags - if tag_index[tag] then - table.insert(tag_index[tag], entry_name) - end + + local text_entry, old_entry = nil, old_db[entry_name] + if help_source == HELP_SOURCES.RENDERED then + text_entry = make_rendered_entry(old_entry, entry_name, kwargs) + elseif help_source == HELP_SOURCES.PLUGIN then + text_entry = make_plugin_entry(old_entry, entry_name, kwargs) + elseif help_source == HELP_SOURCES.SCRIPT then + text_entry = make_script_entry(old_entry, entry_name, kwargs) + elseif help_source == HELP_SOURCES.STUB then + text_entry = make_default_entry(entry_name, HELP_SOURCES.STUB, kwargs) + else + error('unhandled help source: ' .. help_source) end + textdb[entry_name] = text_entry end -- add the builtin commands to the db -local function scan_builtins(old_db, db) +local function scan_builtins(old_db) local entry_types = {[ENTRY_TYPES.BUILTIN]=true, [ENTRY_TYPES.COMMAND]=true} - for _,builtin in ipairs(BUILTINS) do - update_db(old_db, db, - has_rendered_help(builtin) and - HELP_SOURCES.RENDERED or HELP_SOURCES.STUB, - builtin, - {entry_types=entry_types}) + for builtin,canonical in pairs(BUILTINS) do + if canonical == true then canonical = builtin end + update_db(old_db, builtin, canonical, + has_rendered_help(canonical) and + HELP_SOURCES.RENDERED or HELP_SOURCES.STUB, + {entry_types=entry_types}) end end --- scan for plugins and plugin-provided commands and add their help to the db -local function scan_plugins(old_db, db) +-- scan for enableable plugins and plugin-provided commands and add their help +-- to the db +local function scan_plugins(old_db) local plugin_names = dfhack.internal.listPlugins() for _,plugin in ipairs(plugin_names) do local commands = dfhack.internal.listCommands(plugin) - local includes_plugin = false + local includes_plugin, has_commands = false, false for _,command in ipairs(commands) do - local entry_types = {[ENTRY_TYPES.COMMAND]=true} + local kwargs = {entry_types={[ENTRY_TYPES.COMMAND]=true}} if command == plugin then - entry_types[ENTRY_TYPES.PLUGIN]=true + kwargs.entry_types[ENTRY_TYPES.PLUGIN]=true includes_plugin = true end - update_db(old_db, db, - has_rendered_help(command) and + kwargs.short_help = dfhack.internal.getCommandDescription(command) + update_db(old_db, command, plugin, + has_rendered_help(plugin) and HELP_SOURCES.RENDERED or HELP_SOURCES.PLUGIN, - command, {entry_types=entry_types}) + kwargs) + has_commands = true end - if not includes_plugin and - dfhack.internal.isPluginEnableable(plugin) then - update_db(old_db, db, + if not includes_plugin and (has_commands or + dfhack.internal.isPluginEnableable(plugin)) then + update_db(old_db, plugin, plugin, has_rendered_help(plugin) and HELP_SOURCES.RENDERED or HELP_SOURCES.STUB, - plugin, {entry_types={[ENTRY_TYPES.PLUGIN]=true}}) - goto continue + {entry_types={[ENTRY_TYPES.PLUGIN]=true}}) end - ::continue:: end end -- scan for scripts and add their help to the db -local function scan_scripts(old_db, db) +local function scan_scripts(old_db) local entry_types = {[ENTRY_TYPES.COMMAND]=true} for _,script_path in ipairs(dfhack.internal.getScriptPaths()) do local files = dfhack.filesystem.listdir_recursive( @@ -333,12 +354,11 @@ local function scan_scripts(old_db, db) end local dot_index = f.path:find('%.[^.]*$') local entry_name = f.path:sub(1, dot_index - 1) - local script_source = script_path .. '/' .. f.path - update_db(old_db, db, + local source_path = script_path .. '/' .. f.path + update_db(old_db, entry_name, entry_name, has_rendered_help(entry_name) and HELP_SOURCES.RENDERED or HELP_SOURCES.SCRIPT, - entry_name, - {entry_types=entry_types, script_source=script_source}) + {entry_types=entry_types, source_path=source_path}) ::continue:: end ::skip_path:: @@ -370,6 +390,17 @@ local function initialize_tags() end end +local function index_tags() + for entry_name,entry in pairs(entrydb) do + for tag in pairs(textdb[entry.text_entry].tags) do + -- ignore unknown tags + if tag_index[tag] then + table.insert(tag_index[tag], entry_name) + end + end + end +end + -- ensures the db is up to date by scanning all help sources. does not do -- anything if it has already been run within the last 60 seconds. last_refresh_ms = last_refresh_ms or 0 @@ -378,13 +409,14 @@ local function ensure_db() if now_ms - last_refresh_ms < 60000 then return end last_refresh_ms = now_ms - local old_db = db - db, tag_index = {}, {} + local old_db = textdb + textdb, entrydb, tag_index = {}, {}, {} initialize_tags() - scan_builtins(old_db, db) - scan_plugins(old_db, db) - scan_scripts(old_db, db) + scan_builtins(old_db) + scan_plugins(old_db) + scan_scripts(old_db) + index_tags() end --------------------------------------------------------------------------- @@ -415,15 +447,16 @@ end -- returns whether the given string (or list of strings) is an entry in the db function is_entry(str) - return has_keys(str, db) + return has_keys(str, entrydb) end local function get_db_property(entry_name, property) ensure_db() - if not db[entry_name] then + if not entrydb[entry_name] then error(('helpdb entry not found: "%s"'):format(entry_name)) end - return db[entry_name][property] + return entrydb[entry_name][property] or + textdb[entrydb[entry_name].text_entry][property] end -- returns the ~54 char summary blurb associated with the entry @@ -504,11 +537,11 @@ local function sort_by_basename(a, b) end local function matches(entry_name, filter) - local db_entry = db[entry_name] if filter.tag then local matched = false + local tags = get_db_property(entry_name, 'tags') for _,tag in ipairs(filter.tag) do - if db_entry.tags[tag] then + if tags[tag] then matched = true break end @@ -519,8 +552,9 @@ local function matches(entry_name, filter) end if filter.types then local matched = false + local etypes = get_db_property(entry_name, 'entry_types') for _,etype in ipairs(filter.types) do - if db_entry.entry_types[etype] then + if etypes[etype] then matched = true break end @@ -577,7 +611,7 @@ function search_entries(include, exclude) include = normalize_filter(include) exclude = normalize_filter(exclude) local entries = {} - for entry in pairs(db) do + for entry in pairs(entrydb) do if (not include or matches(entry, include)) and (not exclude or not matches(entry, exclude)) then table.insert(entries, entry) @@ -595,7 +629,8 @@ end function is_builtin(command) ensure_db() - return db[command] and db[command].entry_types[ENTRY_TYPES.BUILTIN] + return entrydb[command] and + get_db_property(entry_name, 'entry_types')[ENTRY_TYPES.BUILTIN] end --------------------------------------------------------------------------- @@ -605,7 +640,7 @@ end -- implements the 'help' builtin command function help(entry) ensure_db() - if not db[entry] then + if not entrydb[entry] then dfhack.printerr(('No help entry found for "%s"'):format(entry)) return end From 4b1696f783e46196bf0e5fbb5a2f71551305367a Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 21 Jul 2022 22:36:17 -0700 Subject: [PATCH 269/854] add '?' alias for help --- library/lua/helpdb.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 647d08996..c660a37dd 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -40,6 +40,7 @@ local HELP_SOURCES = { -- builtin command names, with aliases mapped to their canonical form local BUILTINS = { + ['?']='help', alias=true, clear='cls', cls=true, From b3679bef25991fc141cef82db598d0c7af95600d Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 21 Jul 2022 23:21:56 -0700 Subject: [PATCH 270/854] enable index building and update builtin docs --- conf.py | 4 ++-- docs/builtins/alias.rst | 9 +++++---- docs/builtins/clear.rst | 7 ------- docs/builtins/cls.rst | 5 +++-- docs/builtins/devel/dump-rpc.rst | 3 ++- docs/builtins/die.rst | 3 ++- docs/builtins/dir.rst | 7 ------- docs/builtins/disable.rst | 5 +++-- docs/builtins/enable.rst | 11 ++++++----- docs/builtins/fpause.rst | 4 ++-- docs/builtins/help.rst | 4 +++- docs/builtins/hide.rst | 3 ++- docs/builtins/keybinding.rst | 9 +++++---- docs/builtins/kill-lua.rst | 5 +++-- docs/builtins/load.rst | 5 +++-- docs/builtins/ls.rst | 8 ++++---- docs/builtins/man.rst | 7 ------- docs/builtins/plug.rst | 3 ++- docs/builtins/reload.rst | 5 +++-- docs/builtins/sc-script.rst | 6 ++++-- docs/builtins/script.rst | 7 ++++--- docs/builtins/show.rst | 9 +++++---- docs/builtins/tags.rst | 5 +++-- docs/builtins/type.rst | 7 ++++--- docs/builtins/unload.rst | 3 ++- 25 files changed, 72 insertions(+), 72 deletions(-) delete mode 100644 docs/builtins/clear.rst delete mode 100644 docs/builtins/dir.rst delete mode 100644 docs/builtins/man.rst diff --git a/conf.py b/conf.py index 7cf4a3a32..0488a2afe 100644 --- a/conf.py +++ b/conf.py @@ -346,8 +346,8 @@ html_sidebars = { # If false, no module index is generated. html_domain_indices = False -# If false, no index is generated. -html_use_index = False +# If false, no genindex.html is generated. +html_use_index = True html_css_files = [ 'dfhack.css', diff --git a/docs/builtins/alias.rst b/docs/builtins/alias.rst index 325bb9dd7..59669fa39 100644 --- a/docs/builtins/alias.rst +++ b/docs/builtins/alias.rst @@ -4,10 +4,11 @@ alias Tags: system :dfhack-keybind:`alias` -Configure helper aliases for other DFHack commands. Aliases are resolved -immediately after built-in commands, which means that an alias cannot override -a built-in command, but can override a command implemented by a plugin or -script. +:index:`Configure helper aliases for other DFHack commands. +` Aliases are +resolved immediately after built-in commands, which means that an alias cannot +override a built-in command, but can override a command implemented by a plugin +or script. Usage: diff --git a/docs/builtins/clear.rst b/docs/builtins/clear.rst deleted file mode 100644 index 44c4f679a..000000000 --- a/docs/builtins/clear.rst +++ /dev/null @@ -1,7 +0,0 @@ -clear -===== - -Tags: system -:dfhack-keybind:`clear` - -Clear the terminal screen. This command is an alias for `cls`. diff --git a/docs/builtins/cls.rst b/docs/builtins/cls.rst index 0514353bd..bc22b54fc 100644 --- a/docs/builtins/cls.rst +++ b/docs/builtins/cls.rst @@ -4,5 +4,6 @@ cls Tags: system :dfhack-keybind:`cls` -Clear the terminal screen. Can also be invoked as `clear`. Note that this -command does not delete command history. It just clears the text on the screen. +:index:`Clear the terminal screen. ` Can also +be invoked as ``clear``. Note that this command does not delete command history. +It just clears the text on the screen. diff --git a/docs/builtins/devel/dump-rpc.rst b/docs/builtins/devel/dump-rpc.rst index e6a710409..7babfb6a7 100644 --- a/docs/builtins/devel/dump-rpc.rst +++ b/docs/builtins/devel/dump-rpc.rst @@ -4,7 +4,8 @@ devel/dump-rpc Tags: system :dfhack-keybind:`devel/dump-rpc` -Writes RPC endpoint information to the specified file. +:index:`Writes RPC endpoint information to the specified file. +` Usage:: diff --git a/docs/builtins/die.rst b/docs/builtins/die.rst index 7a7414b8c..8f20a825f 100644 --- a/docs/builtins/die.rst +++ b/docs/builtins/die.rst @@ -4,4 +4,5 @@ die Tags: system :dfhack-keybind:`die` -Instantly exits DF without saving. +:index:`Instantly exits DF without saving. +` diff --git a/docs/builtins/dir.rst b/docs/builtins/dir.rst deleted file mode 100644 index 2eca9218c..000000000 --- a/docs/builtins/dir.rst +++ /dev/null @@ -1,7 +0,0 @@ -dir -=== - -Tags: system -:dfhack-keybind:`dir` - -List available DFHack commands. This is an alias of the `ls` command. diff --git a/docs/builtins/disable.rst b/docs/builtins/disable.rst index 413b8325f..3fb989328 100644 --- a/docs/builtins/disable.rst +++ b/docs/builtins/disable.rst @@ -4,8 +4,9 @@ disable Tags: system :dfhack-keybind:`disable` -Deactivate a DFHack tool that has some persistent effect. See the `enable` -command for more info. +:index:`Deactivate a DFHack tool that has some persistent effect. +` See the +`enable` command for more info. Usage:: diff --git a/docs/builtins/enable.rst b/docs/builtins/enable.rst index 78534a98c..3a4164485 100644 --- a/docs/builtins/enable.rst +++ b/docs/builtins/enable.rst @@ -4,11 +4,12 @@ enable Tags: system :dfhack-keybind:`enable` -Activate a DFHack tool that has some persistent effect. Many plugins and scripts -can be in a distinct enabled or disabled state. Some of them activate and -deactivate automatically depending on the contents of the world raws. Others -store their state in world data. However a number of them have to be enabled -globally, and the init file is the right place to do it. +:index:`Activate a DFHack tool that has some persistent effect. +` Many plugins +and scripts can be in a distinct enabled or disabled state. Some of them +activate and deactivate automatically depending on the contents of the world +raws. Others store their state in world data. However a number of them have to +be enabled globally, and the init file is the right place to do it. Most such plugins or scripts support the built-in ``enable`` and `disable` commands. Calling them at any time without arguments prints a list of enabled diff --git a/docs/builtins/fpause.rst b/docs/builtins/fpause.rst index 60459848e..2e797129b 100644 --- a/docs/builtins/fpause.rst +++ b/docs/builtins/fpause.rst @@ -4,8 +4,8 @@ fpause Tags: system :dfhack-keybind:`fpause` -Forces DF to pause. This is useful when your FPS drops below 1 and you lose -control of the game. +:index:`Forces DF to pause. ` This is useful when +your FPS drops below 1 and you lose control of the game. Usage:: diff --git a/docs/builtins/help.rst b/docs/builtins/help.rst index 49c010fec..9a1f3f1b8 100644 --- a/docs/builtins/help.rst +++ b/docs/builtins/help.rst @@ -4,7 +4,9 @@ help Tags: system :dfhack-keybind:`help` -Display help about a command or plugin. +:index:`Display help about a command or plugin. +` Can also be invoked as ``?`` +or ``man`` (short for "manual"). Usage:: diff --git a/docs/builtins/hide.rst b/docs/builtins/hide.rst index 6b391972f..51da0cc4f 100644 --- a/docs/builtins/hide.rst +++ b/docs/builtins/hide.rst @@ -4,7 +4,8 @@ hide Tags: system :dfhack-keybind:`hide` -Hides the DFHack terminal window. You can show it again with the `show` +:index:`Hides the DFHack terminal window. +` You can show it again with the `show` command, though you'll need to use it from a `keybinding` set beforehand or the in-game `command-prompt`. diff --git a/docs/builtins/keybinding.rst b/docs/builtins/keybinding.rst index 72ceaaf71..f591ece34 100644 --- a/docs/builtins/keybinding.rst +++ b/docs/builtins/keybinding.rst @@ -4,12 +4,13 @@ keybinding Tags: system :dfhack-keybind:`keybinding` -Create hotkeys that will run DFHack commands. Like any other command it can be -used at any time from the console, but bindings are not remembered between runs -of the game unless re-created in `dfhack.init`. +:index:`Create hotkeys that will run DFHack commands. +` Like any other +command, it can be used at any time from the console, but bindings are not +remembered between runs of the game unless re-created in `dfhack.init`. Hotkeys can be any combinations of Ctrl/Alt/Shift with A-Z, 0-9, F1-F12, or -``\```. +``\``` (the key below the ``Esc`` key. Usage: diff --git a/docs/builtins/kill-lua.rst b/docs/builtins/kill-lua.rst index 875376952..998277f96 100644 --- a/docs/builtins/kill-lua.rst +++ b/docs/builtins/kill-lua.rst @@ -4,8 +4,9 @@ kill-lua Tags: system :dfhack-keybind:`kill-lua` -Gracefully stops any currently-running Lua scripts. Use this command to stop -a misbehaving script that appears to be stuck. +:index:`Gracefully stops any currently-running Lua scripts. +` Use this +command to stop a misbehaving script that appears to be stuck. Usage:: diff --git a/docs/builtins/load.rst b/docs/builtins/load.rst index cac760c2a..e84afe7d9 100644 --- a/docs/builtins/load.rst +++ b/docs/builtins/load.rst @@ -4,8 +4,9 @@ load Tags: system :dfhack-keybind:`load` -Load and register a plugin library. Also see `unload` and `reload` for related -actions. +:index:`Load and register a plugin library. +` Also see `unload` and `reload` for +related actions. Usage:: diff --git a/docs/builtins/ls.rst b/docs/builtins/ls.rst index 6875083f5..a6d2e637e 100644 --- a/docs/builtins/ls.rst +++ b/docs/builtins/ls.rst @@ -4,10 +4,10 @@ ls Tags: system :dfhack-keybind:`ls` -List available DFHack commands. In order to group related commands, each command -is associated with a list of tags. You can filter the listed commands by a tag -or a substring of the command name. The `dir` command is an alias of this -command. +:index:`List available DFHack commands. ` +In order to group related commands, each command is associated with a list of +tags. You can filter the listed commands by a tag or a substring of the +command name. Can also be invoked as ``dir``. Usage: diff --git a/docs/builtins/man.rst b/docs/builtins/man.rst deleted file mode 100644 index 3b5b52515..000000000 --- a/docs/builtins/man.rst +++ /dev/null @@ -1,7 +0,0 @@ -man -=== - -Tags: system -:dfhack-keybind:`man` - -An alias for the `help` command. diff --git a/docs/builtins/plug.rst b/docs/builtins/plug.rst index 8f714b7cf..38b37aa84 100644 --- a/docs/builtins/plug.rst +++ b/docs/builtins/plug.rst @@ -4,7 +4,8 @@ plug Tags: system :dfhack-keybind:`plug` -Lists available plugins and whether they are enabled. +:index:`Lists available plugins and whether they are enabled. +` Usage: diff --git a/docs/builtins/reload.rst b/docs/builtins/reload.rst index d61e5c2fd..2fa817e3d 100644 --- a/docs/builtins/reload.rst +++ b/docs/builtins/reload.rst @@ -4,8 +4,9 @@ reload Tags: system :dfhack-keybind:`reload` -Reload a loaded plugin. Developer use this command to reload a plugin that they -are actively modifying. Also see `load` and `unload` for related actions. +:index:`Reload a loaded plugin. ` Developers +use this command to reload a plugin that they are actively modifying. Also see +`load` and `unload` for related actions. Usage:: diff --git a/docs/builtins/sc-script.rst b/docs/builtins/sc-script.rst index 2c80e1130..eac65b463 100644 --- a/docs/builtins/sc-script.rst +++ b/docs/builtins/sc-script.rst @@ -4,8 +4,10 @@ sc-script Tags: system :dfhack-keybind:`sc-script` -Runs commands when game state changes occur. This is similar to the static -`init-files` but is slightly more flexible since it can be set dynamically. +:index:`Run commands when game state changes occur. +` This is similar to +the static `init-files` but is slightly more flexible since it can be set +dynamically. Usage: diff --git a/docs/builtins/script.rst b/docs/builtins/script.rst index d69f3bbc7..17797ade7 100644 --- a/docs/builtins/script.rst +++ b/docs/builtins/script.rst @@ -4,9 +4,10 @@ script Tags: system :dfhack-keybind:`script` -Executes a batch file of DFHack commands. It reads a text file and runs each -line as a DFHack command as if it had been typed in by the user - treating the -input like `an init file `. +:index:`Executes a batch file of DFHack commands. +` It reads a text file and +runs each line as a DFHack command as if it had been typed in by the user -- +treating the input like `an init file `. Some other tools, such as `autobutcher` and `workflow`, export their settings as the commands to create them - which can later be reloaded with ``script``. diff --git a/docs/builtins/show.rst b/docs/builtins/show.rst index e192f857c..81fda6943 100644 --- a/docs/builtins/show.rst +++ b/docs/builtins/show.rst @@ -4,10 +4,11 @@ show Tags: system :dfhack-keybind:`show` -Unhides the DFHack terminal window. Useful if you have hidden the terminal with -`hide` and you want it back. Since the terminal window won't be available to run -this command, you'll need to use it from a `keybinding` set beforehand or the -in-game `command-prompt`. +:index:`Unhides the DFHack terminal window. +` Useful if you have hidden the +terminal with `hide` and you want it back. Since the terminal window won't be +available to run this command, you'll need to use it from a `keybinding` set +beforehand or the in-game `command-prompt`. Only available on Windows. diff --git a/docs/builtins/tags.rst b/docs/builtins/tags.rst index 3d7933fca..ad8f03b28 100644 --- a/docs/builtins/tags.rst +++ b/docs/builtins/tags.rst @@ -4,8 +4,9 @@ tags Tags: system :dfhack-keybind:`tags` -List the strings that DFHack tools can be tagged with. You can find groups of -related tools by passing the tag name to the `ls` command. +:index:`List the strings that DFHack tools can be tagged with. +` You can find +groups of related tools by passing the tag name to the `ls` command. Usage:: diff --git a/docs/builtins/type.rst b/docs/builtins/type.rst index 16325c90f..2c16a3078 100644 --- a/docs/builtins/type.rst +++ b/docs/builtins/type.rst @@ -4,9 +4,10 @@ type Tags: system :dfhack-keybind:`type` -Describes how a command is implemented. DFHack commands can be provided by -plugins, scripts, or by the core library itself. The ``type`` command can tell -you which is the source of a particular command. +:index:`Describes how a command is implemented. +` DFHack commands can be provided +by plugins, scripts, or by the core library itself. The ``type`` command can +tell you which is the source of a particular command. Usage:: diff --git a/docs/builtins/unload.rst b/docs/builtins/unload.rst index 99eed500c..15a0fa789 100644 --- a/docs/builtins/unload.rst +++ b/docs/builtins/unload.rst @@ -4,7 +4,8 @@ unload Tags: system :dfhack-keybind:`unload` -Unload a plugin from memory. Also see `load` and `reload` for related actions. +:index:`Unload a plugin from memory. ` +Also see `load` and `reload` for related actions. Usage:: From 6b9803daaf2c985d5f7e0943925dc8b08f9d9818 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 00:05:53 -0700 Subject: [PATCH 271/854] update docs up to the end of the b's --- docs/plugins/3dveins.rst | 8 ++-- docs/plugins/add-spatter.rst | 1 - docs/plugins/autochop.rst | 1 - docs/plugins/autoclothing.rst | 5 ++- docs/plugins/autodump-destroy-here.rst | 13 ------- docs/plugins/autodump-destroy-item.rst | 16 -------- docs/plugins/autodump.rst | 37 ++++++++++++------ docs/plugins/autofarm.rst | 9 +++-- docs/plugins/autogems-reload.rst | 12 ------ docs/plugins/autogems.rst | 16 +++++--- docs/plugins/autohauler.rst | 9 +++-- docs/plugins/autolabor.rst | 6 ++- docs/plugins/automaterial.rst | 1 - docs/plugins/automelt.rst | 1 - docs/plugins/autotrade.rst | 1 - docs/plugins/blueprint.rst | 7 ++-- docs/plugins/building-hacks.rst | 1 - docs/plugins/buildingplan.rst | 13 ++++--- docs/plugins/burrow.rst | 49 ------------------------ docs/plugins/burrows.rst | 52 +++++++++++++++++++++++--- docs/plugins/digl.rst | 20 ---------- docs/plugins/diglx.rst | 20 ---------- docs/plugins/digv.rst | 20 ---------- docs/plugins/digvx.rst | 20 ---------- 24 files changed, 116 insertions(+), 222 deletions(-) delete mode 100644 docs/plugins/autodump-destroy-here.rst delete mode 100644 docs/plugins/autodump-destroy-item.rst delete mode 100644 docs/plugins/autogems-reload.rst delete mode 100644 docs/plugins/burrow.rst delete mode 100644 docs/plugins/digl.rst delete mode 100644 docs/plugins/diglx.rst delete mode 100644 docs/plugins/digv.rst delete mode 100644 docs/plugins/digvx.rst diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 89c241df6..ff230679b 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -4,9 +4,11 @@ Tags: :dfhack-keybind:`3dveins` -Rewrites layer veins to expand in 3D space. Existing, flat veins are removed -and new 3D veins that naturally span z-levels are generated in their place. -The transformation preserves the mineral counts reported by `prospect`. +:index:`Rewrites layer veins to expand in 3D space. +<3dveins; Rewrites layer veins to expand in 3D space.>` Existing, flat veins +are removed and new 3D veins that naturally span z-levels are generated in +their place. The transformation preserves the mineral counts reported by +`prospect`. Usage:: diff --git a/docs/plugins/add-spatter.rst b/docs/plugins/add-spatter.rst index 92808017c..0c5c551b3 100644 --- a/docs/plugins/add-spatter.rst +++ b/docs/plugins/add-spatter.rst @@ -2,7 +2,6 @@ add-spatter =========== Tags: -:dfhack-keybind:`add-spatter` Make tagged reactions produce contaminants. The plugin is intended to give some use to all those poisons that can be bought from caravans. It automatically diff --git a/docs/plugins/autochop.rst b/docs/plugins/autochop.rst index 704b97143..3fc65144e 100644 --- a/docs/plugins/autochop.rst +++ b/docs/plugins/autochop.rst @@ -2,7 +2,6 @@ autochop ======== Tags: -:dfhack-keybind:`autochop` Auto-harvest trees when low on stockpiled logs. This plugin can designate trees for chopping when your stocks are low on logs. diff --git a/docs/plugins/autoclothing.rst b/docs/plugins/autoclothing.rst index a8cc001dd..df937c338 100644 --- a/docs/plugins/autoclothing.rst +++ b/docs/plugins/autoclothing.rst @@ -4,8 +4,9 @@ autoclothing Tags: :dfhack-keybind:`autoclothing` -Automatically manage clothing work orders. It allows you to set how many of each -clothing type every citizen should have. +:index:`Automatically manage clothing work orders. +` It allows you to +set how many of each clothing type every citizen should have. Usage:: diff --git a/docs/plugins/autodump-destroy-here.rst b/docs/plugins/autodump-destroy-here.rst deleted file mode 100644 index 35356166c..000000000 --- a/docs/plugins/autodump-destroy-here.rst +++ /dev/null @@ -1,13 +0,0 @@ -autodump-destroy-here -===================== - -Tags: -:dfhack-keybind:`autodump-destroy-here` - -Destroy items marked for dumping under cursor. If called again before the game -is resumed, cancels destruction of the items. This is an alias for the -`autodump` command ``autodump destroy-here``, intended for use as a keybinding. - -Usage:: - - autodump-destroy-here diff --git a/docs/plugins/autodump-destroy-item.rst b/docs/plugins/autodump-destroy-item.rst deleted file mode 100644 index 9447590d0..000000000 --- a/docs/plugins/autodump-destroy-item.rst +++ /dev/null @@ -1,16 +0,0 @@ -autodump-destroy-item -===================== - -Tags: -:dfhack-keybind:`autodump-destroy-item` - -Destroy the selected item. The item may be selected in the :kbd:`k` list or in -the container item list. If called again before the game is resumed, cancels -destruction of the item. - -This command is intended for use as a keybinding. See the `autodump` command -for other dumping/destroying options. - -Usage:: - - autodump-destroy-item diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst index 5ac0d1cd5..45f5f90e1 100644 --- a/docs/plugins/autodump.rst +++ b/docs/plugins/autodump.rst @@ -3,15 +3,17 @@ autodump Tags: :dfhack-keybind:`autodump` +:dfhack-keybind:`autodump-destroy-here` +:dfhack-keybind:`autodump-destroy-item` -Quickly designate or teleport items to be dumped. When `enabled `, this -plugin adds an option to the :kbd:`q` menu for stockpiles. When the ``autodump`` -option is selected for the stockpile, any items placed in the stockpile will -automatically be designated to be dumped. +Automatically set items in a stockpile to be dumped. When `enabled `, +this plugin adds an option to the :kbd:`q` menu for stockpiles. When the +``autodump`` option is selected for the stockpile, any items placed in the +stockpile will automatically be designated to be dumped. -When invoked as a command, it can instantly move all items designated to be -dumped to the tile under the cursor. After moving the items, the dump flag is -unset and the forbid flag is set, just as if it had been dumped normally. Be +When invoked as a command, it can instantly move all unforbidden items marked +for dumping to the tile under the cursor. After moving the items, the dump flag +is unset and the forbid flag is set, just as if it had been dumped normally. Be aware that dwarves that are en route to pick up the item for dumping may still come and move the item to your dump zone. @@ -21,6 +23,15 @@ Usage:: enable autodump autodump [] + autodump-destroy-here + autodump-destroy-item + +``autodump-destroy-here`` is an alias for ``autodump destroy-here`` and is +intended for use as a keybinding. + +``autodump-destroy-item`` destroys only the selected item. The item may be +selected in the :kbd:`k` list or in the container item list. If called again +before the game is resumed, cancels destruction of the item. Options ------- @@ -28,10 +39,10 @@ Options - ``destroy`` Destroy instead of dumping. Doesn't require a cursor. If ``autodump`` is called again with this option before the game is resumed, it cancels - the destroy action. + pending destroy actions. - ``destroy-here`` - As ``destroy``, but only the selected item in the :kbd:`k` list, or inside a - container. + :index:`Destroy items marked for dumping under the cursor. + ` - ``visible`` Only process items that are not hidden. - ``hidden`` @@ -43,6 +54,10 @@ Examples -------- - ``autodump`` - Teleports all unforbidden items marked for dumping to the cursor position. + :index:`Teleports items marked for dumping to the cursor position. + ` - ``autodump destroy`` Destroys all unforbidden items marked for dumping +- ``autodump-destroy-item`` + :index:`Destroys the selected item. + ` diff --git a/docs/plugins/autofarm.rst b/docs/plugins/autofarm.rst index 9a3e16d43..0294b574b 100644 --- a/docs/plugins/autofarm.rst +++ b/docs/plugins/autofarm.rst @@ -4,10 +4,11 @@ autofarm Tags: :dfhack-keybind:`autofarm` -Automatically manage farm crop selection. This plugin periodically scans your -plant stocks and assigns crops to your farm plots based on which plant stocks -are low (as long as you have the appropriate seeds). The target threshold for -each crop type is configurable. +:index:`Automatically manage farm crop selection. +` This plugin periodically +scans your plant stocks and assigns crops to your farm plots based on which +plant stocks are low (as long as you have the appropriate seeds). The target +threshold for each crop type is configurable. Usage: diff --git a/docs/plugins/autogems-reload.rst b/docs/plugins/autogems-reload.rst deleted file mode 100644 index 0988496cb..000000000 --- a/docs/plugins/autogems-reload.rst +++ /dev/null @@ -1,12 +0,0 @@ -autogems-reload -=============== - -Tags: -:dfhack-keybind:`autogems-reload` - -Reloads the autogems configuration file. You might need to do this if you have -manually modified the contents while the game is running. - -Usage:: - - autogems-reload diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index 724752217..215c3b69f 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -2,14 +2,20 @@ autogems ======== Tags: -:dfhack-keybind:`autogems` +:dfhack-keybind:`autogems-reload` Automatically cut rough gems. This plugin periodically scans your stocks of rough gems and creates manager orders for cutting them at a Jeweler's Workshop. -Usage:: +Usage: - enable autogems - -Run `gui/autogems` for a configuration UI, or access the new ``Auto Cut Gems`` +- ``enable autogems`` + Enables the plugin +- ``autogems-reload`` + :index:`Reloads the autogems configuration file. + ` You might need + to do this if you have manually modified the contents while the game is + running. + +Run `gui/autogems` for a configuration UI, or access the ``Auto Cut Gems`` option from the Current Workshop Orders screen (:kbd:`o`-:kbd:`W`). diff --git a/docs/plugins/autohauler.rst b/docs/plugins/autohauler.rst index f28044479..1dce8a692 100644 --- a/docs/plugins/autohauler.rst +++ b/docs/plugins/autohauler.rst @@ -4,10 +4,11 @@ autohauler Tags: :dfhack-keybind:`autohauler` -Automatically manage hauling labors. Similar to `autolabor`, but instead of -managing all labors, ``autohauler`` only addresses hauling labors, leaving the -assignment of skilled labors entirely up to you. You can use the in-game -`manipulator` UI or an external tool like Dwarf Therapist to do so. +:index:`Automatically manage hauling labors. +` Similar to `autolabor`, but +instead of managing all labors, ``autohauler`` only addresses hauling labors, +leaving the assignment of skilled labors entirely up to you. You can use the +in-game `manipulator` UI or an external tool like Dwarf Therapist to do so. Idle dwarves who are not on active military duty will be assigned the hauling labors; everyone else (including those currently hauling) will have the hauling diff --git a/docs/plugins/autolabor.rst b/docs/plugins/autolabor.rst index f402703c8..7696efe71 100644 --- a/docs/plugins/autolabor.rst +++ b/docs/plugins/autolabor.rst @@ -4,8 +4,10 @@ autolabor Tags: :dfhack-keybind:`autolabor` -Automatically manage dwarf labors. Autolabor attempts to keep as many dwarves as -possible busy while allowing dwarves to specialize in specific skills. +:index:`Automatically manage dwarf labors. +` Autolabor attempts to keep as +many dwarves as possible busy while allowing dwarves to specialize in specific +skills. Autolabor frequently checks how many jobs of each type are available and sets labors proportionally in order to get them all done quickly. Labors with diff --git a/docs/plugins/automaterial.rst b/docs/plugins/automaterial.rst index 429a25d70..6c5e94c12 100644 --- a/docs/plugins/automaterial.rst +++ b/docs/plugins/automaterial.rst @@ -2,7 +2,6 @@ automaterial ============ Tags: -:dfhack-keybind:`automaterial` Sorts building materials by recent usage. This makes building constructions (walls, floors, fortifications, etc) much easier by saving you from having to diff --git a/docs/plugins/automelt.rst b/docs/plugins/automelt.rst index b16368ac5..38788ee93 100644 --- a/docs/plugins/automelt.rst +++ b/docs/plugins/automelt.rst @@ -2,7 +2,6 @@ automelt ======== Tags: -:dfhack-keybind:`automelt` Quickly designate items to be melted. When `enabled `, this plugin adds an option to the :kbd:`q` menu for stockpiles. When the ``automelt`` option is diff --git a/docs/plugins/autotrade.rst b/docs/plugins/autotrade.rst index 405621b2b..5a5053b8f 100644 --- a/docs/plugins/autotrade.rst +++ b/docs/plugins/autotrade.rst @@ -2,7 +2,6 @@ autotrade ========= Tags: -:dfhack-keybind:`autotrade` Quickly designate items to be traded. When `enabled `, this plugin adds an option to the :kbd:`q` menu for stockpiles. When the ``autotrade`` option is diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst index 9d74fa47a..3825de7b2 100644 --- a/docs/plugins/blueprint.rst +++ b/docs/plugins/blueprint.rst @@ -4,9 +4,10 @@ blueprint Tags: :dfhack-keybind:`blueprint` -Record a live game map in a quickfort blueprint. With ``blueprint``, you can -export the structure of a portion of your fortress in a blueprint file that you -(or anyone else) can later play back with `quickfort`. +:index:`Record a live game map in a quickfort blueprint. +` With +``blueprint``, you can export the structure of a portion of your fortress in a +blueprint file that you (or anyone else) can later play back with `quickfort`. Blueprints are ``.csv`` or ``.xlsx`` files created in the ``blueprints`` subdirectory of your DF folder. The map area to turn into a blueprint is either diff --git a/docs/plugins/building-hacks.rst b/docs/plugins/building-hacks.rst index 78e94fba6..0c20a474f 100644 --- a/docs/plugins/building-hacks.rst +++ b/docs/plugins/building-hacks.rst @@ -2,7 +2,6 @@ building-hacks ============== Tags: -:dfhack-keybind:`building-hacks` Allows mods to create and manage powered workshops. diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index 62a239e3d..ac73acd49 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -4,12 +4,13 @@ buildingplan Tags: :dfhack-keybind:`buildingplan` -Plan building construction before you have materials. This plugin adds a -planning mode for building placement. You can then place furniture, -constructions, and other buildings before the required materials are available, -and they will be created in a suspended state. Buildingplan will periodically -scan for appropriate items, and the jobs will be unsuspended when the items are -available. +:index:`Plan building construction before you have materials. +` This +plugin adds a planning mode for building placement. You can then place +furniture, constructions, and other buildings before the required materials are +available, and they will be created in a suspended state. Buildingplan will +periodically scan for appropriate items, and the jobs will be unsuspended when +the items are available. This is very useful when combined with manager work orders or `workflow` -- you can set a constraint to always have one or two doors/beds/tables/chairs/etc. diff --git a/docs/plugins/burrow.rst b/docs/plugins/burrow.rst deleted file mode 100644 index 879a7cc78..000000000 --- a/docs/plugins/burrow.rst +++ /dev/null @@ -1,49 +0,0 @@ -burrow -====== - -Tags: -:dfhack-keybind:`burrow` - -Quick commands for burrow control. Allows manipulating burrows and automated -burrow expansion while digging. - -Usage: - -- ``burrows enable auto-grow`` - When a wall inside a burrow with a name ending in '+' is dug out, the burrow - will be extended to newly-revealed adjacent walls. This final '+' may be - omitted in burrow name args of other ``burrows`` commands. Note that digging - 1-wide corridors with the miner inside the burrow is SLOW. Be sure to also - run ``enable burrow`` for this feature to work. -- ``burrows disable auto-grow`` - Disables auto-grow processing. -- ``burrows clear-unit [ ...]`` - Remove all units from the named burrows. -- ``burrows clear-tiles [ ...]`` - Remove all tiles from the named burrows. -- ``burrows set-units target-burrow [ ...]`` - Clear all units from the target burrow, then add units from the named source - burrows. -- ``burrows add-units target-burrow [ ...]`` - Add units from the source burrows to the target. -- ``burrows remove-units target-burrow [ ...]`` - Remove units in source burrows from the target. -- ``burrows set-tiles target-burrow [ ...]`` - Clear target burrow tiles and adds tiles from the names source burrows. -- ``burrows add-tiles target-burrow [ ...]`` - Add tiles from the source burrows to the target. -- ``burrows remove-tiles target-burrow [ ...]`` - Remove tiles in source burrows from the target. - -In place of a source burrow, you can use one of the following keywords: - -- ``ABOVE_GROUND`` -- ``SUBTERRANEAN`` -- ``INSIDE`` -- ``OUTSIDE`` -- ``LIGHT`` -- ``DARK`` -- ``HIDDEN`` -- ``REVEALED`` - -to add tiles with the given properties. diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst index 9bf375fa8..bc80e5bb1 100644 --- a/docs/plugins/burrows.rst +++ b/docs/plugins/burrows.rst @@ -2,17 +2,57 @@ burrows ======= Tags: -:dfhack-keybind:`burrows` +:dfhack-keybind:`burrow` Auto-expand burrows as you dig. When a wall inside a burrow with a name ending in ``+`` is dug out, the burrow will be extended to newly-revealed adjacent walls. Note that digging 1-wide corridors with the miner inside the burrow is SLOW. -Usage:: +You can also use the ``burrow`` command to +:index:`Quickly add units/tiles to burrows. +` - enable burrows - burrows enable auto-grow +Usage: -Both of the above commands need to be run for the auto-grow functionality to -work. See the `burrow` command for more burrow-related tools. +- ``enable burrows`` + Enable the plugin for the auto-grow feature (see + ``burrow enable auto-grow`` below) +- ``burrow enable auto-grow`` + When a wall inside a burrow with a name ending in '+' is dug out, the burrow + will be extended to newly-revealed adjacent walls. This final '+' may be + omitted in burrow name args of other ``burrows`` commands. Note that digging + 1-wide corridors with the miner inside the burrow is SLOW. Be sure to also + run ``enable burrows`` for this feature to work. +- ``burrow disable auto-grow`` + Disables auto-grow processing. +- ``burrow clear-unit [ ...]`` + Remove all units from the named burrows. +- ``burrow clear-tiles [ ...]`` + Remove all tiles from the named burrows. +- ``burrow set-units target-burrow [ ...]`` + Clear all units from the target burrow, then add units from the named source + burrows. +- ``burrow add-units target-burrow [ ...]`` + Add units from the source burrows to the target. +- ``burrow remove-units target-burrow [ ...]`` + Remove units in source burrows from the target. +- ``burrow set-tiles target-burrow [ ...]`` + Clear target burrow tiles and adds tiles from the names source burrows. +- ``burrow add-tiles target-burrow [ ...]`` + Add tiles from the source burrows to the target. +- ``burrow remove-tiles target-burrow [ ...]`` + Remove tiles in source burrows from the target. + +In place of a source burrow, you can use one of the following keywords: + +- ``ABOVE_GROUND`` +- ``SUBTERRANEAN`` +- ``INSIDE`` +- ``OUTSIDE`` +- ``LIGHT`` +- ``DARK`` +- ``HIDDEN`` +- ``REVEALED`` + +to add tiles with the given properties. diff --git a/docs/plugins/digl.rst b/docs/plugins/digl.rst deleted file mode 100644 index f09626d0c..000000000 --- a/docs/plugins/digl.rst +++ /dev/null @@ -1,20 +0,0 @@ -dig -=== -This plugin makes many automated or complicated dig patterns easy. - -Basic commands: - -:digv: Designate all of the selected vein for digging. -:digvx: Also cross z-levels, digging stairs as needed. Alias for ``digv x``. -:digl: Like ``digv``, for layer stone. Also supports an ``undo`` option - to remove designations, for if you accidentally set 50 levels at once. -:diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. - -:dfhack-keybind:`digv` - -.. note:: - - All commands implemented by the `dig` plugin (listed by ``ls dig``) support - specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, - where ``#`` is a number from 1 to 7. If a priority is not specified, the - priority selected in-game is used as the default. diff --git a/docs/plugins/diglx.rst b/docs/plugins/diglx.rst deleted file mode 100644 index f09626d0c..000000000 --- a/docs/plugins/diglx.rst +++ /dev/null @@ -1,20 +0,0 @@ -dig -=== -This plugin makes many automated or complicated dig patterns easy. - -Basic commands: - -:digv: Designate all of the selected vein for digging. -:digvx: Also cross z-levels, digging stairs as needed. Alias for ``digv x``. -:digl: Like ``digv``, for layer stone. Also supports an ``undo`` option - to remove designations, for if you accidentally set 50 levels at once. -:diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. - -:dfhack-keybind:`digv` - -.. note:: - - All commands implemented by the `dig` plugin (listed by ``ls dig``) support - specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, - where ``#`` is a number from 1 to 7. If a priority is not specified, the - priority selected in-game is used as the default. diff --git a/docs/plugins/digv.rst b/docs/plugins/digv.rst deleted file mode 100644 index f09626d0c..000000000 --- a/docs/plugins/digv.rst +++ /dev/null @@ -1,20 +0,0 @@ -dig -=== -This plugin makes many automated or complicated dig patterns easy. - -Basic commands: - -:digv: Designate all of the selected vein for digging. -:digvx: Also cross z-levels, digging stairs as needed. Alias for ``digv x``. -:digl: Like ``digv``, for layer stone. Also supports an ``undo`` option - to remove designations, for if you accidentally set 50 levels at once. -:diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. - -:dfhack-keybind:`digv` - -.. note:: - - All commands implemented by the `dig` plugin (listed by ``ls dig``) support - specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, - where ``#`` is a number from 1 to 7. If a priority is not specified, the - priority selected in-game is used as the default. diff --git a/docs/plugins/digvx.rst b/docs/plugins/digvx.rst deleted file mode 100644 index f09626d0c..000000000 --- a/docs/plugins/digvx.rst +++ /dev/null @@ -1,20 +0,0 @@ -dig -=== -This plugin makes many automated or complicated dig patterns easy. - -Basic commands: - -:digv: Designate all of the selected vein for digging. -:digvx: Also cross z-levels, digging stairs as needed. Alias for ``digv x``. -:digl: Like ``digv``, for layer stone. Also supports an ``undo`` option - to remove designations, for if you accidentally set 50 levels at once. -:diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. - -:dfhack-keybind:`digv` - -.. note:: - - All commands implemented by the `dig` plugin (listed by ``ls dig``) support - specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, - where ``#`` is a number from 1 to 7. If a priority is not specified, the - priority selected in-game is used as the default. From 53cdf57043d34e78f599e682198b93caa2bef8a4 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 00:08:03 -0700 Subject: [PATCH 272/854] add missing digv label --- docs/plugins/dig.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst index f09626d0c..2b1e96a18 100644 --- a/docs/plugins/dig.rst +++ b/docs/plugins/dig.rst @@ -1,3 +1,5 @@ +.. _digv: + dig === This plugin makes many automated or complicated dig patterns easy. From 3522b89f5ce66b92d7f92538f9b8e640bdfc8ada Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 22 Jul 2022 14:20:01 +0000 Subject: [PATCH 273/854] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/plugins/autogems.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index 215c3b69f..e1dd294e3 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -16,6 +16,6 @@ Usage: ` You might need to do this if you have manually modified the contents while the game is running. - + Run `gui/autogems` for a configuration UI, or access the ``Auto Cut Gems`` option from the Current Workshop Orders screen (:kbd:`o`-:kbd:`W`). From 9c7731f072f4998d7f530e0c14b316b3e3ddde9f Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 09:19:11 -0700 Subject: [PATCH 274/854] remove reference to dfhack.init-example --- docs/Installing.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/Installing.rst b/docs/Installing.rst index 5b564f45f..05db198b8 100644 --- a/docs/Installing.rst +++ b/docs/Installing.rst @@ -96,13 +96,14 @@ When you `download DFHack `, you will end up with a release archive operating system should have built-in utilities capable of extracting files from these archives. -The release archives contain several files and folders, including a ``hack`` -folder, a ``dfhack-config`` folder, and a ``dfhack.init-example`` file. To -install DFHack, copy all of the files from the DFHack archive into the root DF -folder, which should already include a ``data`` folder and a ``raw`` folder, -among other things. Some packs and other redistributions of Dwarf Fortress may -place DF in another folder, so ensure that the ``hack`` folder ends up next to -the ``data`` folder. +The release archives contain several folders, including a ``hack`` folder where +DFHack binary and system data is stored, a ``dfhack-config`` folder where user +data and configuration is stored, and a ``blueprints`` folder where `quickfort` +blueprints are stored. To install DFHack, copy all of the files from the DFHack +archive into the root DF folder, which should already include a ``data`` folder +and a ``raw`` folder, among other things. Some packs and other redistributions +of Dwarf Fortress may place DF in another folder, so ensure that the ``hack`` +folder ends up next to the ``data`` folder. .. note:: From bd581581267a57865b24fa73d58b4efc428bdd88 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 09:56:23 -0700 Subject: [PATCH 275/854] get rid of tool TOC, use genindex also add a stub role for dfhack-tag --- conf.py | 13 +++++++++++++ docs/Introduction.rst | 6 ++++++ docs/Tags.rst | 42 +++++++++++++++++++++++------------------- docs/index-tools.rst | 15 --------------- index.rst | 29 +++++------------------------ 5 files changed, 47 insertions(+), 58 deletions(-) delete mode 100644 docs/index-tools.rst diff --git a/conf.py b/conf.py index 0488a2afe..abbd1dbb3 100644 --- a/conf.py +++ b/conf.py @@ -82,7 +82,18 @@ def dfhack_keybind_role_func(role, rawtext, text, lineno, inliner, return [newnode], [] +# pylint:disable=unused-argument,dangerous-default-value,too-many-arguments +def dfhack_tag_role_func(role, rawtext, text, lineno, inliner, + options={}, content=[]): + """Custom role parser for DFHack tags.""" + roles.set_classes(options) + # TODO: link to generated tag index page + newnode = nodes.inline(text, text) + return [newnode], [] + + roles.register_canonical_role('dfhack-keybind', dfhack_keybind_role_func) +roles.register_canonical_role('dfhack-tag', dfhack_tag_role_func) # -- Autodoc for DFhack plugins and scripts ------------------------------- @@ -128,6 +139,7 @@ def write_tool_docs(): footer. """ for k in DOC_ALL_DIRS: + header = ':orphan:\n' label = ('.. _{name}:\n\n').format(name=k[0]) # TODO: can we autogenerate the :dfhack-keybind: line? it would go beneath # the tool header, which is currently in the middle of the included file. @@ -143,6 +155,7 @@ def write_tool_docs(): os.makedirs(os.path.join('docs/tools', os.path.dirname(k[0])), mode=0o755, exist_ok=True) with open('docs/tools/{}.rst'.format(k[0]), mode) as outfile: + outfile.write(header) if k[0] != 'search' and k[0] != 'stonesense': outfile.write(label) outfile.write(include) diff --git a/docs/Introduction.rst b/docs/Introduction.rst index f0dc7a4b6..449f2d38a 100644 --- a/docs/Introduction.rst +++ b/docs/Introduction.rst @@ -59,6 +59,12 @@ used by the DFHack console. * Finally, some commands are persistent once enabled, and will sit in the background managing or changing some aspect of the game if you `enable` them. +.. note:: + In order to avoid user confusion, as a matter of policy all GUI tools + display the word :guilabel:`DFHack` on the screen somewhere while active. + + When that is not appropriate because they merely add keybinding hints to + existing DF screens, they deliberately use red instead of green for the key. Getting help ============ diff --git a/docs/Tags.rst b/docs/Tags.rst index 56e5af650..de4e4900b 100644 --- a/docs/Tags.rst +++ b/docs/Tags.rst @@ -1,23 +1,27 @@ .. _tags: -Tags -==== +Tool categories +=============== -This is the list of tags and their descriptions. +Related DFHack tools are grouped by tags to make them easier to find. If you'd +like to see the full list of commands, please refer to the `index `. -- adventure: Tools relevant to adventure mode -- fort: Tools relevant to fort mode -- legends: Tools relevant to legends mode -- items: Tools that create or modify in-game items -- units: Tools that create or modify units -- jobs: Tools that create or modify jobs -- labors: Tools that deal with labor assignment -- auto: Tools that automatically manage some aspect of your fortress -- map: Map modification -- system: Tools related to working with DFHack commands or the core DFHack library -- productivity: Tools that help you do things that you could do manually, but using the tool is better and faster -- animals: Tools that help you manage animals -- fix: Tools that fix specific bugs -- inspection: Tools that let you inspect game data -- buildings/furniture: Tools that help you work wtih placing or configuring buildings and furniture -- quickfort: Tools that are involved in creating and playing back blueprints +These are the DFHack tool categories. Note that a tool may belong to more than +one category. + +- :dfhack-tag:`adventure`: Tools relevant to adventure mode +- :dfhack-tag:`fort`: Tools relevant to fort mode +- :dfhack-tag:`legends`: Tools relevant to legends mode +- :dfhack-tag:`items`: Tools that create or modify in-game items +- :dfhack-tag:`units`: Tools that create or modify units +- :dfhack-tag:`jobs`: Tools that create or modify jobs +- :dfhack-tag:`labors`: Tools that deal with labor assignment +- :dfhack-tag:`auto`: Tools that automatically manage some aspect of your fortress +- :dfhack-tag:`map`: Map modification +- :dfhack-tag:`system`: Tools related to working with DFHack commands or the core DFHack library +- :dfhack-tag:`productivity`: Tools that help you do things that you could do manually, but using the tool is better and faster +- :dfhack-tag:`animals`: Tools that help you manage animals +- :dfhack-tag:`fix`: Tools that fix specific bugs +- :dfhack-tag:`inspection`: Tools that let you inspect game data +- :dfhack-tag:`buildings/furniture`: Tools that help you work wtih placing or configuring buildings and furniture +- :dfhack-tag:`quickfort`: Tools that are involved in creating and playing back blueprints diff --git a/docs/index-tools.rst b/docs/index-tools.rst deleted file mode 100644 index a9c540d66..000000000 --- a/docs/index-tools.rst +++ /dev/null @@ -1,15 +0,0 @@ -.. _tools-index: - -================== -DFHack Tools Index -================== - -These pages contain information about the plugins, scripts, and built-in -commands distributed with DFHack. - -.. toctree:: - :titlesonly: - :glob: - - /docs/tools/* - /docs/tools/*/* diff --git a/index.rst b/index.rst index 3e5cdec5d..843797e4e 100644 --- a/index.rst +++ b/index.rst @@ -15,10 +15,9 @@ Quick Links * `Downloads `_ * `Installation guide ` -* `Source code `_ - (**important:** read `compile` before attempting to build from source) -* `Bay 12 forums thread `_ -* `Bug tracker `_ +* `Getting help ` +* :source:`Source code <>` + (**important:** read `compile` before attempting to build from source.) User Manual =========== @@ -30,25 +29,7 @@ User Manual /docs/Installing /docs/Support /docs/Core + /docs/Tags /docs/guides/index - /docs/index-about /docs/index-dev - -Tools -===== - -DFHack commands, plugins, and scripts are grouped by tags to make it easier to -find groups of related tools. - -.. note:: - In order to avoid user confusion, as a matter of policy all GUI tools - display the word :guilabel:`DFHack` on the screen somewhere while active. - - When that is not appropriate because they merely add keybinding hints to - existing DF screens, they deliberately use red instead of green for the key. - -.. toctree:: - :maxdepth: 1 - - /docs/Tags - /docs/index-tools + /docs/index-about From 3175e8b33d3cce98f775c0f7348e0064a5907704 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 10:01:04 -0700 Subject: [PATCH 276/854] update tags and text for builtins --- docs/builtins/alias.rst | 2 +- docs/builtins/cls.rst | 2 +- docs/builtins/devel/dump-rpc.rst | 4 ++-- docs/builtins/die.rst | 6 +++--- docs/builtins/disable.rst | 2 +- docs/builtins/enable.rst | 2 +- docs/builtins/fpause.rst | 2 +- docs/builtins/help.rst | 2 +- docs/builtins/hide.rst | 6 +++--- docs/builtins/keybinding.rst | 2 +- docs/builtins/kill-lua.rst | 2 +- docs/builtins/load.rst | 2 +- docs/builtins/ls.rst | 2 +- docs/builtins/plug.rst | 6 +++--- docs/builtins/reload.rst | 2 +- docs/builtins/sc-script.rst | 2 +- docs/builtins/script.rst | 6 +++--- docs/builtins/show.rst | 2 +- docs/builtins/tags.rst | 2 +- docs/builtins/type.rst | 6 +++--- docs/builtins/unload.rst | 2 +- 21 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/builtins/alias.rst b/docs/builtins/alias.rst index 59669fa39..6de550875 100644 --- a/docs/builtins/alias.rst +++ b/docs/builtins/alias.rst @@ -1,7 +1,7 @@ alias ===== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`alias` :index:`Configure helper aliases for other DFHack commands. diff --git a/docs/builtins/cls.rst b/docs/builtins/cls.rst index bc22b54fc..5c1189951 100644 --- a/docs/builtins/cls.rst +++ b/docs/builtins/cls.rst @@ -1,7 +1,7 @@ cls === -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`cls` :index:`Clear the terminal screen. ` Can also diff --git a/docs/builtins/devel/dump-rpc.rst b/docs/builtins/devel/dump-rpc.rst index 7babfb6a7..05f1d4e9e 100644 --- a/docs/builtins/devel/dump-rpc.rst +++ b/docs/builtins/devel/dump-rpc.rst @@ -1,10 +1,10 @@ devel/dump-rpc ============== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`devel/dump-rpc` -:index:`Writes RPC endpoint information to the specified file. +:index:`Write RPC endpoint information to the specified file. ` Usage:: diff --git a/docs/builtins/die.rst b/docs/builtins/die.rst index 8f20a825f..c2ea083fd 100644 --- a/docs/builtins/die.rst +++ b/docs/builtins/die.rst @@ -1,8 +1,8 @@ die === -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`die` -:index:`Instantly exits DF without saving. -` +:index:`Instantly exit DF without saving. +` diff --git a/docs/builtins/disable.rst b/docs/builtins/disable.rst index 3fb989328..1e2336545 100644 --- a/docs/builtins/disable.rst +++ b/docs/builtins/disable.rst @@ -1,7 +1,7 @@ disable ======= -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`disable` :index:`Deactivate a DFHack tool that has some persistent effect. diff --git a/docs/builtins/enable.rst b/docs/builtins/enable.rst index 3a4164485..4630c2004 100644 --- a/docs/builtins/enable.rst +++ b/docs/builtins/enable.rst @@ -1,7 +1,7 @@ enable ====== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`enable` :index:`Activate a DFHack tool that has some persistent effect. diff --git a/docs/builtins/fpause.rst b/docs/builtins/fpause.rst index 2e797129b..927206e8d 100644 --- a/docs/builtins/fpause.rst +++ b/docs/builtins/fpause.rst @@ -1,7 +1,7 @@ fpause ====== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`fpause` :index:`Forces DF to pause. ` This is useful when diff --git a/docs/builtins/help.rst b/docs/builtins/help.rst index 9a1f3f1b8..6249bdbe9 100644 --- a/docs/builtins/help.rst +++ b/docs/builtins/help.rst @@ -1,7 +1,7 @@ help ==== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`help` :index:`Display help about a command or plugin. diff --git a/docs/builtins/hide.rst b/docs/builtins/hide.rst index 51da0cc4f..3c79f9ff0 100644 --- a/docs/builtins/hide.rst +++ b/docs/builtins/hide.rst @@ -1,11 +1,11 @@ hide ==== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`hide` -:index:`Hides the DFHack terminal window. -` You can show it again with the `show` +:index:`Hide the DFHack terminal window. +` You can show it again with the `show` command, though you'll need to use it from a `keybinding` set beforehand or the in-game `command-prompt`. diff --git a/docs/builtins/keybinding.rst b/docs/builtins/keybinding.rst index f591ece34..27ab86da6 100644 --- a/docs/builtins/keybinding.rst +++ b/docs/builtins/keybinding.rst @@ -1,7 +1,7 @@ keybinding ========== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`keybinding` :index:`Create hotkeys that will run DFHack commands. diff --git a/docs/builtins/kill-lua.rst b/docs/builtins/kill-lua.rst index 998277f96..3f4bbf3c4 100644 --- a/docs/builtins/kill-lua.rst +++ b/docs/builtins/kill-lua.rst @@ -1,7 +1,7 @@ kill-lua ======== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`kill-lua` :index:`Gracefully stops any currently-running Lua scripts. diff --git a/docs/builtins/load.rst b/docs/builtins/load.rst index e84afe7d9..22e8067f3 100644 --- a/docs/builtins/load.rst +++ b/docs/builtins/load.rst @@ -1,7 +1,7 @@ load ==== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`load` :index:`Load and register a plugin library. diff --git a/docs/builtins/ls.rst b/docs/builtins/ls.rst index a6d2e637e..775ccfc63 100644 --- a/docs/builtins/ls.rst +++ b/docs/builtins/ls.rst @@ -1,7 +1,7 @@ ls == -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`ls` :index:`List available DFHack commands. ` diff --git a/docs/builtins/plug.rst b/docs/builtins/plug.rst index 38b37aa84..7dbbf0115 100644 --- a/docs/builtins/plug.rst +++ b/docs/builtins/plug.rst @@ -1,11 +1,11 @@ plug ==== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`plug` -:index:`Lists available plugins and whether they are enabled. -` +:index:`List available plugins and whether they are enabled. +` Usage: diff --git a/docs/builtins/reload.rst b/docs/builtins/reload.rst index 2fa817e3d..a5ab60859 100644 --- a/docs/builtins/reload.rst +++ b/docs/builtins/reload.rst @@ -1,7 +1,7 @@ reload ====== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`reload` :index:`Reload a loaded plugin. ` Developers diff --git a/docs/builtins/sc-script.rst b/docs/builtins/sc-script.rst index eac65b463..feabe4c6f 100644 --- a/docs/builtins/sc-script.rst +++ b/docs/builtins/sc-script.rst @@ -1,7 +1,7 @@ sc-script ========= -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`sc-script` :index:`Run commands when game state changes occur. diff --git a/docs/builtins/script.rst b/docs/builtins/script.rst index 17797ade7..b83f19171 100644 --- a/docs/builtins/script.rst +++ b/docs/builtins/script.rst @@ -1,11 +1,11 @@ script ====== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`script` -:index:`Executes a batch file of DFHack commands. -` It reads a text file and +:index:`Execute a batch file of DFHack commands. +` It reads a text file and runs each line as a DFHack command as if it had been typed in by the user -- treating the input like `an init file `. diff --git a/docs/builtins/show.rst b/docs/builtins/show.rst index 81fda6943..e337bbbb6 100644 --- a/docs/builtins/show.rst +++ b/docs/builtins/show.rst @@ -1,7 +1,7 @@ show ==== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`show` :index:`Unhides the DFHack terminal window. diff --git a/docs/builtins/tags.rst b/docs/builtins/tags.rst index ad8f03b28..cc5c86a4b 100644 --- a/docs/builtins/tags.rst +++ b/docs/builtins/tags.rst @@ -1,7 +1,7 @@ tags ==== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`tags` :index:`List the strings that DFHack tools can be tagged with. diff --git a/docs/builtins/type.rst b/docs/builtins/type.rst index 2c16a3078..33ef7562f 100644 --- a/docs/builtins/type.rst +++ b/docs/builtins/type.rst @@ -1,11 +1,11 @@ type ==== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`type` -:index:`Describes how a command is implemented. -` DFHack commands can be provided +:index:`Describe how a command is implemented. +` DFHack commands can be provided by plugins, scripts, or by the core library itself. The ``type`` command can tell you which is the source of a particular command. diff --git a/docs/builtins/unload.rst b/docs/builtins/unload.rst index 15a0fa789..3d1575c20 100644 --- a/docs/builtins/unload.rst +++ b/docs/builtins/unload.rst @@ -1,7 +1,7 @@ unload ====== -Tags: system +Tags: :dfhack-tag:`system` :dfhack-keybind:`unload` :index:`Unload a plugin from memory. ` From d637c874963f6f9e1045d8946730aefdbc28e00b Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 10:14:50 -0700 Subject: [PATCH 277/854] ensure all plugins are indexed --- docs/plugins/3dveins.rst | 6 ++++-- docs/plugins/add-spatter.rst | 9 +++++---- docs/plugins/autochop.rst | 5 +++-- docs/plugins/autodump.rst | 9 +++++---- docs/plugins/autogems.rst | 5 +++-- docs/plugins/automaterial.rst | 7 ++++--- docs/plugins/automelt.rst | 9 +++++---- docs/plugins/autotrade.rst | 9 +++++---- docs/plugins/building-hacks.rst | 3 ++- docs/plugins/burrows.rst | 11 ++++++----- 10 files changed, 42 insertions(+), 31 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index ff230679b..3c1276b46 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -4,8 +4,8 @@ Tags: :dfhack-keybind:`3dveins` -:index:`Rewrites layer veins to expand in 3D space. -<3dveins; Rewrites layer veins to expand in 3D space.>` Existing, flat veins +:index:`Rewrite layer veins to expand in 3D space. +<3dveins; Rewrite layer veins to expand in 3D space.>` Existing, flat veins are removed and new 3D veins that naturally span z-levels are generated in their place. The transformation preserves the mineral counts reported by `prospect`. @@ -19,6 +19,8 @@ The ``verbose`` option prints out extra information to the console. Example ------- +:: + 3dveins New veins are generated using 3D Perlin noise in order to produce a layout that diff --git a/docs/plugins/add-spatter.rst b/docs/plugins/add-spatter.rst index 0c5c551b3..38a9f91f0 100644 --- a/docs/plugins/add-spatter.rst +++ b/docs/plugins/add-spatter.rst @@ -3,9 +3,10 @@ add-spatter Tags: -Make tagged reactions produce contaminants. The plugin is intended to give some -use to all those poisons that can be bought from caravans. It automatically -enables itself when you load a world with reactions that include names starting -with ``SPATTER_ADD_``. These reactions will then produce contaminants on items +:index:`Make tagged reactions produce contaminants. +` Give some use to all +those poisons that can be bought from caravans! The plugin automatically enables +itself when you load a world with reactions that include names starting with +``SPATTER_ADD_``. These reactions will then produce contaminants on items instead of improvements. The contaminants are immune to being washed away by water or destroyed by `clean`. diff --git a/docs/plugins/autochop.rst b/docs/plugins/autochop.rst index 3fc65144e..3a2284aef 100644 --- a/docs/plugins/autochop.rst +++ b/docs/plugins/autochop.rst @@ -3,8 +3,9 @@ autochop Tags: -Auto-harvest trees when low on stockpiled logs. This plugin can designate trees -for chopping when your stocks are low on logs. +:index:`Auto-harvest trees when low on stockpiled logs. +` This plugin can +designate trees for chopping when your stocks are low on logs. Usage:: diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst index 45f5f90e1..a47878c34 100644 --- a/docs/plugins/autodump.rst +++ b/docs/plugins/autodump.rst @@ -6,10 +6,11 @@ Tags: :dfhack-keybind:`autodump-destroy-here` :dfhack-keybind:`autodump-destroy-item` -Automatically set items in a stockpile to be dumped. When `enabled `, -this plugin adds an option to the :kbd:`q` menu for stockpiles. When the -``autodump`` option is selected for the stockpile, any items placed in the -stockpile will automatically be designated to be dumped. +:index:`Automatically set items in a stockpile to be dumped. +` When +`enabled `, this plugin adds an option to the :kbd:`q` menu for +stockpiles. When the ``autodump`` option is selected for the stockpile, any +items placed in the stockpile will automatically be designated to be dumped. When invoked as a command, it can instantly move all unforbidden items marked for dumping to the tile under the cursor. After moving the items, the dump flag diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index e1dd294e3..bdfd51864 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -4,8 +4,9 @@ autogems Tags: :dfhack-keybind:`autogems-reload` -Automatically cut rough gems. This plugin periodically scans your stocks of -rough gems and creates manager orders for cutting them at a Jeweler's Workshop. +:index:`Automatically cut rough gems. ` +This plugin periodically scans your stocks of rough gems and creates manager +orders for cutting them at a Jeweler's Workshop. Usage: diff --git a/docs/plugins/automaterial.rst b/docs/plugins/automaterial.rst index 6c5e94c12..e51276772 100644 --- a/docs/plugins/automaterial.rst +++ b/docs/plugins/automaterial.rst @@ -3,9 +3,10 @@ automaterial Tags: -Sorts building materials by recent usage. This makes building constructions -(walls, floors, fortifications, etc) much easier by saving you from having to -trawl through long lists of materials each time you place one. +:index:`Sorts building materials by recent usage. +; Sorts building materials by recent usage.>` This makes building +constructions (walls, floors, fortifications, etc) much easier by saving you +from having to trawl through long lists of materials each time you place one. It moves the last used material for a given construction type to the top of the list, if there are any left. So if you build a wall with chalk blocks, the next diff --git a/docs/plugins/automelt.rst b/docs/plugins/automelt.rst index 38788ee93..b51a431c0 100644 --- a/docs/plugins/automelt.rst +++ b/docs/plugins/automelt.rst @@ -3,10 +3,11 @@ automelt Tags: -Quickly designate items to be melted. When `enabled `, this plugin adds -an option to the :kbd:`q` menu for stockpiles. When the ``automelt`` option is -selected for the stockpile, any items placed in the stockpile will automatically -be designated to be melted. +:index:`Quickly designate items to be melted. +` When `enabled `, this +plugin adds an option to the :kbd:`q` menu for stockpiles. When the ``automelt`` +option is selected for the stockpile, any items placed in the stockpile will +automatically be designated to be melted. Usage:: diff --git a/docs/plugins/autotrade.rst b/docs/plugins/autotrade.rst index 5a5053b8f..ed2d65bc5 100644 --- a/docs/plugins/autotrade.rst +++ b/docs/plugins/autotrade.rst @@ -3,10 +3,11 @@ autotrade Tags: -Quickly designate items to be traded. When `enabled `, this plugin adds -an option to the :kbd:`q` menu for stockpiles. When the ``autotrade`` option is -selected for the stockpile, any items placed in the stockpile will automatically -be designated to be traded. +:index:`Quickly designate items to be traded. +` When `enabled `, +this plugin adds an option to the :kbd:`q` menu for stockpiles. When the +``autotrade`` option is selected for the stockpile, any items placed in the +stockpile will automatically be designated to be traded. Usage:: diff --git a/docs/plugins/building-hacks.rst b/docs/plugins/building-hacks.rst index 0c20a474f..abd6ac4b4 100644 --- a/docs/plugins/building-hacks.rst +++ b/docs/plugins/building-hacks.rst @@ -3,7 +3,8 @@ building-hacks Tags: -Allows mods to create and manage powered workshops. +:index:`Allows mods to create and manage powered workshops. +` Usage:: diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst index bc80e5bb1..6ab26caa2 100644 --- a/docs/plugins/burrows.rst +++ b/docs/plugins/burrows.rst @@ -4,13 +4,14 @@ burrows Tags: :dfhack-keybind:`burrow` -Auto-expand burrows as you dig. When a wall inside a burrow with a name ending -in ``+`` is dug out, the burrow will be extended to newly-revealed adjacent -walls. Note that digging 1-wide corridors with the miner inside the burrow is -SLOW. +:index:`Auto-expand burrows as you dig. +` When a wall inside a burrow +with a name ending in ``+`` is dug out, the burrow will be extended to +newly-revealed adjacent walls. Note that digging 1-wide corridors with the miner +inside the burrow is **SLOW**. You can also use the ``burrow`` command to -:index:`Quickly add units/tiles to burrows. +:index:`quickly add units/tiles to burrows. ` Usage: From b38ccfe03d1493a10b9d0c339468e25f871e8d80 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 10:18:32 -0700 Subject: [PATCH 278/854] fix typo in automaterial docs --- docs/plugins/automaterial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/automaterial.rst b/docs/plugins/automaterial.rst index e51276772..7f8355b49 100644 --- a/docs/plugins/automaterial.rst +++ b/docs/plugins/automaterial.rst @@ -4,7 +4,7 @@ automaterial Tags: :index:`Sorts building materials by recent usage. -; Sorts building materials by recent usage.>` This makes building +` This makes building constructions (walls, floors, fortifications, etc) much easier by saving you from having to trawl through long lists of materials each time you place one. From 6a31b316dc694e6a7838b001d649074ee61532b5 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 14:37:24 -0700 Subject: [PATCH 279/854] add structure for category indices --- conf.py | 35 +++++++++++++++++++++++++++++++---- docs/Categories.rst | 13 +++++++++++++ docs/Tags.rst | 20 ++++++-------------- 3 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 docs/Categories.rst diff --git a/conf.py b/conf.py index abbd1dbb3..e80b5a027 100644 --- a/conf.py +++ b/conf.py @@ -126,10 +126,38 @@ def doc_all_dirs(): DOC_ALL_DIRS = doc_all_dirs() + +def get_tags(): + tags = [] + tag_re = re.compile(r'- :dfhack-tag:`([^`]+)`: (.*)') + with open('docs/Tags.rst') as f: + lines = f.readlines() + for line in lines: + m = re.match(tag_re, line.strip()) + if m: + tags.append((m.group(1), m.group(2))) + return tags + + +def get_open_mode(): + return 'w' if sys.version_info.major > 2 else 'wb' + + def generate_tag_indices(): - #TODO: generate docs/tags/.rst with the tag description and links to the - # tools that have that tag os.makedirs('docs/tags', mode=0o755, exist_ok=True) + with open('docs/tags/index.rst', get_open_mode()) as topidx: + #topidx.write(':orphan:\n\n') + for tag_tuple in get_tags(): + tag = tag_tuple[0] + with open(('docs/tags/{name}.rst').format(name=tag), + get_open_mode()) as tagidx: + #tagidx.write(':orphan:\n\n') + tagidx.write('TODO: add links to the tools that have this tag') + topidx.write(('.. _tag/{name}:\n\n').format(name=tag)) + topidx.write(('{name}\n').format(name=tag)) + topidx.write(('{underline}\n').format(underline='-'*len(tag))) + topidx.write(('{desc}\n\n').format(desc=tag_tuple[1])) + topidx.write(('.. include:: /docs/tags/{name}.rst\n\n').format(name=tag)) def write_tool_docs(): @@ -151,10 +179,9 @@ def write_tool_docs(): # TODO: generate a footer with links to tools that share at least one # tag with this tool. Just the tool names, strung across the bottom of # the page in one long wrapped line, similar to how the wiki does it - mode = 'w' if sys.version_info.major > 2 else 'wb' os.makedirs(os.path.join('docs/tools', os.path.dirname(k[0])), mode=0o755, exist_ok=True) - with open('docs/tools/{}.rst'.format(k[0]), mode) as outfile: + with open('docs/tools/{}.rst'.format(k[0]), get_open_mode()) as outfile: outfile.write(header) if k[0] != 'search' and k[0] != 'stonesense': outfile.write(label) diff --git a/docs/Categories.rst b/docs/Categories.rst new file mode 100644 index 000000000..541f89195 --- /dev/null +++ b/docs/Categories.rst @@ -0,0 +1,13 @@ +.. _categories: + +Tool categories +=============== + +DFHack tools are grouped to make them easier to find. Note that a tool can +belong to more than one category. If you'd like to see the full list of tools, +please refer to the `index `. + +.. contents:: Contents + :local: + +.. include:: tags/index.rst diff --git a/docs/Tags.rst b/docs/Tags.rst index de4e4900b..7afbe0a02 100644 --- a/docs/Tags.rst +++ b/docs/Tags.rst @@ -1,17 +1,8 @@ -.. _tags: +:orphan: -Tool categories -=============== - -Related DFHack tools are grouped by tags to make them easier to find. If you'd -like to see the full list of commands, please refer to the `index `. - -These are the DFHack tool categories. Note that a tool may belong to more than -one category. - -- :dfhack-tag:`adventure`: Tools relevant to adventure mode -- :dfhack-tag:`fort`: Tools relevant to fort mode -- :dfhack-tag:`legends`: Tools relevant to legends mode +- :dfhack-tag:`adventure`: Tools that are useful while in adventure mode +- :dfhack-tag:`fort`: Tools that are useful while in fort mode +- :dfhack-tag:`legends`: Tools that are useful while in legends mode - :dfhack-tag:`items`: Tools that create or modify in-game items - :dfhack-tag:`units`: Tools that create or modify units - :dfhack-tag:`jobs`: Tools that create or modify jobs @@ -23,5 +14,6 @@ one category. - :dfhack-tag:`animals`: Tools that help you manage animals - :dfhack-tag:`fix`: Tools that fix specific bugs - :dfhack-tag:`inspection`: Tools that let you inspect game data -- :dfhack-tag:`buildings/furniture`: Tools that help you work wtih placing or configuring buildings and furniture +- :dfhack-tag:`buildings`: Tools that help you work wtih placing or configuring buildings and furniture - :dfhack-tag:`quickfort`: Tools that are involved in creating and playing back blueprints +- :dfhack-tag:`dev`: Tools useful for develpers and modders From 2fd6d528ce084c87ebe1c2c923a734470a05cf7c Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 14:37:47 -0700 Subject: [PATCH 280/854] move support docs to the intro page but leave an orphan link to the new section in case external links still point to the old page --- docs/Introduction.rst | 34 ++++++++++++++++++++++++++++++++-- docs/Support.rst | 35 ++--------------------------------- index.rst | 3 +-- 3 files changed, 35 insertions(+), 37 deletions(-) diff --git a/docs/Introduction.rst b/docs/Introduction.rst index 449f2d38a..ad24f8f43 100644 --- a/docs/Introduction.rst +++ b/docs/Introduction.rst @@ -21,7 +21,7 @@ enhancements by default, and more can be enabled. There are also many tools You can even add third-party scripts and plugins to do almost anything! For modders, DFHack makes many things possible. Custom reactions, new -interactions, magic creature abilities, and more can be set through `DFHack tools ` +interactions, magic creature abilities, and more can be set through `DFHack tools ` and custom raws. Non-standard DFHack scripts and inits can be stored in the raw directory, making raws or saves fully self-contained for distribution - or for coexistence in a single DF install, even with incompatible components. @@ -66,6 +66,36 @@ used by the DFHack console. When that is not appropriate because they merely add keybinding hints to existing DF screens, they deliberately use red instead of green for the key. +.. _support: + Getting help ============ -There are several support channels available - see `support` for details. + +DFHack has several ways to get help online, including: + +- The `DFHack Discord server `__ +- The ``#dfhack`` IRC channel on `Libera `__ +- GitHub: + - for bugs, use the :issue:`issue tracker <>` + - for more open-ended questions, use the `discussion board + `__. Note that this is a + relatively-new feature as of 2021, but maintainers should still be + notified of any discussions here. +- The `DFHack thread on the Bay 12 Forum `__ + +Some additional, but less DFHack-specific, places where questions may be answered include: + +- The `/r/dwarffortress `_ questions thread on Reddit +- If you are using a starter pack, the relevant thread on the `Bay 12 Forum `__ - + see the :wiki:`DF Wiki ` for a list of these threads + +When reaching out to any support channels regarding problems with DFHack, please +remember to provide enough details for others to identify the issue. For +instance, specific error messages (copied text or screenshots) are helpful, as +well as any steps you can follow to reproduce the problem. Sometimes, log output +from ``stderr.log`` in the DF folder can point to the cause of issues as well. + +Some common questions may also be answered in documentation, including: + +- This documentation (`online here `__; search functionality available `here `) +- :wiki:`The DF wiki <>` diff --git a/docs/Support.rst b/docs/Support.rst index 00e055baa..af8bd583c 100644 --- a/docs/Support.rst +++ b/docs/Support.rst @@ -1,34 +1,3 @@ -.. _support: +:orphan: -=============== -Getting Support -=============== - -DFHack has several ways to get help online, including: - -- The `DFHack Discord server `__ -- The ``#dfhack`` IRC channel on `Libera `__ -- GitHub: - - for bugs, use the :issue:`issue tracker <>` - - for more open-ended questions, use the `discussion board - `__. Note that this is a - relatively-new feature as of 2021, but maintainers should still be - notified of any discussions here. -- The `DFHack thread on the Bay 12 Forum `__ - -Some additional, but less DFHack-specific, places where questions may be answered include: - -- The `/r/dwarffortress `_ questions thread on Reddit -- If you are using a starter pack, the relevant thread on the `Bay 12 Forum `__ - - see the :wiki:`DF Wiki ` for a list of these threads - -When reaching out to any support channels regarding problems with DFHack, please -remember to provide enough details for others to identify the issue. For -instance, specific error messages (copied text or screenshots) are helpful, as -well as any steps you can follow to reproduce the problem. Sometimes, log output -from ``stderr.log`` in the DF folder can point to the cause of issues as well. - -Some common questions may also be answered in documentation, including: - -- This documentation (`online here `__; search functionality available `here `) -- :wiki:`The DF wiki <>` +Please continue to the new `support` page. diff --git a/index.rst b/index.rst index 843797e4e..d627267f0 100644 --- a/index.rst +++ b/index.rst @@ -27,9 +27,8 @@ User Manual /docs/Introduction /docs/Installing - /docs/Support /docs/Core - /docs/Tags + /docs/Categories /docs/guides/index /docs/index-dev /docs/index-about From 67520258482bdd94729982f05b94ce6db2899f3b Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 14:38:16 -0700 Subject: [PATCH 281/854] fix up some index links --- docs/Core.rst | 4 ++-- docs/Dev-intro.rst | 9 ++++----- docs/Memory-research.rst | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/Core.rst b/docs/Core.rst index f7ba0824b..dd00885d0 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -26,7 +26,7 @@ DFHack commands can be implemented in any of three ways: more flexible about versions, and easier to distribute. Most third-party DFHack addons are scripts. -All tools distributed with DFHack are documented `here `. +All tools distributed with DFHack are documented `here `. Using DFHack Commands ===================== @@ -185,7 +185,7 @@ where ``*`` can be any string, including the empty string. A world being loaded can mean a fortress, an adventurer, or legends mode. These files are best used for non-persistent commands, such as setting -a `fix ` script to run on `repeat`. +a :dfhack-tag:`fix` script to run on `repeat`. .. _onMapLoad.init: diff --git a/docs/Dev-intro.rst b/docs/Dev-intro.rst index 6f46d86c5..09a68a0f9 100644 --- a/docs/Dev-intro.rst +++ b/docs/Dev-intro.rst @@ -51,11 +51,10 @@ is more complete and currently better-documented, however. Referring to existing scripts as well as the API documentation can be helpful when developing new scripts. -`Scripts included in DFHack ` live in a separate -`scripts repository `_. This can be found in -the ``scripts`` submodule if you have -`cloned DFHack `, or the ``hack/scripts`` folder -of an installed copy of DFHack. +Scripts included in DFHack live in a separate +:source-scripts:`scripts repository <>`. This can be found in the ``scripts`` +submodule if you have `cloned DFHack `, or the +``hack/scripts`` folder of an installed copy of DFHack. Core ---- diff --git a/docs/Memory-research.rst b/docs/Memory-research.rst index bc7b45fd7..091f1c8e7 100644 --- a/docs/Memory-research.rst +++ b/docs/Memory-research.rst @@ -63,7 +63,7 @@ are not built by default, but can be built by setting the ``BUILD_DEVEL`` Scripts ~~~~~~~ -Several `development scripts ` can be useful for memory research. +Several :dfhack-tag:`development tools ` can be useful for memory research. These include (but are not limited to): - `devel/dump-offsets` From e8ffa55dfe98ecd8003514eada5531fa30d35c03 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 15:16:57 -0700 Subject: [PATCH 282/854] use a label instead of a custom role since I can't seem to figure out how to inject a link to a label via the role processing code --- conf.py | 13 +----------- docs/Categories.rst | 4 ++-- docs/Core.rst | 2 +- docs/Memory-research.rst | 2 +- docs/Tags.rst | 34 ++++++++++++++++---------------- docs/builtins/alias.rst | 2 +- docs/builtins/cls.rst | 2 +- docs/builtins/devel/dump-rpc.rst | 2 +- docs/builtins/die.rst | 2 +- docs/builtins/disable.rst | 2 +- docs/builtins/enable.rst | 2 +- docs/builtins/fpause.rst | 2 +- docs/builtins/help.rst | 2 +- docs/builtins/hide.rst | 2 +- docs/builtins/keybinding.rst | 2 +- docs/builtins/kill-lua.rst | 2 +- docs/builtins/load.rst | 2 +- docs/builtins/ls.rst | 2 +- docs/builtins/plug.rst | 2 +- docs/builtins/reload.rst | 2 +- docs/builtins/sc-script.rst | 2 +- docs/builtins/script.rst | 2 +- docs/builtins/show.rst | 2 +- docs/builtins/tags.rst | 2 +- docs/builtins/type.rst | 2 +- docs/builtins/unload.rst | 2 +- 26 files changed, 43 insertions(+), 54 deletions(-) diff --git a/conf.py b/conf.py index e80b5a027..854693ad0 100644 --- a/conf.py +++ b/conf.py @@ -82,18 +82,7 @@ def dfhack_keybind_role_func(role, rawtext, text, lineno, inliner, return [newnode], [] -# pylint:disable=unused-argument,dangerous-default-value,too-many-arguments -def dfhack_tag_role_func(role, rawtext, text, lineno, inliner, - options={}, content=[]): - """Custom role parser for DFHack tags.""" - roles.set_classes(options) - # TODO: link to generated tag index page - newnode = nodes.inline(text, text) - return [newnode], [] - - roles.register_canonical_role('dfhack-keybind', dfhack_keybind_role_func) -roles.register_canonical_role('dfhack-tag', dfhack_tag_role_func) # -- Autodoc for DFhack plugins and scripts ------------------------------- @@ -129,7 +118,7 @@ DOC_ALL_DIRS = doc_all_dirs() def get_tags(): tags = [] - tag_re = re.compile(r'- :dfhack-tag:`([^`]+)`: (.*)') + tag_re = re.compile(r'- `tag/([^`]+)`: (.*)') with open('docs/Tags.rst') as f: lines = f.readlines() for line in lines: diff --git a/docs/Categories.rst b/docs/Categories.rst index 541f89195..46fc71bdf 100644 --- a/docs/Categories.rst +++ b/docs/Categories.rst @@ -4,8 +4,8 @@ Tool categories =============== DFHack tools are grouped to make them easier to find. Note that a tool can -belong to more than one category. If you'd like to see the full list of tools, -please refer to the `index `. +belong to more than one category. If you'd like to see the full list of tools +in one flat list, please refer to the `index `. .. contents:: Contents :local: diff --git a/docs/Core.rst b/docs/Core.rst index dd00885d0..025e951e6 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -185,7 +185,7 @@ where ``*`` can be any string, including the empty string. A world being loaded can mean a fortress, an adventurer, or legends mode. These files are best used for non-persistent commands, such as setting -a :dfhack-tag:`fix` script to run on `repeat`. +a `tag/fix` script to run on `repeat`. .. _onMapLoad.init: diff --git a/docs/Memory-research.rst b/docs/Memory-research.rst index 091f1c8e7..d9c0833e3 100644 --- a/docs/Memory-research.rst +++ b/docs/Memory-research.rst @@ -63,7 +63,7 @@ are not built by default, but can be built by setting the ``BUILD_DEVEL`` Scripts ~~~~~~~ -Several :dfhack-tag:`development tools ` can be useful for memory research. +Several `development tools ` can be useful for memory research. These include (but are not limited to): - `devel/dump-offsets` diff --git a/docs/Tags.rst b/docs/Tags.rst index 7afbe0a02..432632ba1 100644 --- a/docs/Tags.rst +++ b/docs/Tags.rst @@ -1,19 +1,19 @@ :orphan: -- :dfhack-tag:`adventure`: Tools that are useful while in adventure mode -- :dfhack-tag:`fort`: Tools that are useful while in fort mode -- :dfhack-tag:`legends`: Tools that are useful while in legends mode -- :dfhack-tag:`items`: Tools that create or modify in-game items -- :dfhack-tag:`units`: Tools that create or modify units -- :dfhack-tag:`jobs`: Tools that create or modify jobs -- :dfhack-tag:`labors`: Tools that deal with labor assignment -- :dfhack-tag:`auto`: Tools that automatically manage some aspect of your fortress -- :dfhack-tag:`map`: Map modification -- :dfhack-tag:`system`: Tools related to working with DFHack commands or the core DFHack library -- :dfhack-tag:`productivity`: Tools that help you do things that you could do manually, but using the tool is better and faster -- :dfhack-tag:`animals`: Tools that help you manage animals -- :dfhack-tag:`fix`: Tools that fix specific bugs -- :dfhack-tag:`inspection`: Tools that let you inspect game data -- :dfhack-tag:`buildings`: Tools that help you work wtih placing or configuring buildings and furniture -- :dfhack-tag:`quickfort`: Tools that are involved in creating and playing back blueprints -- :dfhack-tag:`dev`: Tools useful for develpers and modders +- `tag/adventure`: Tools that are useful while in adventure mode +- `tag/fort`: Tools that are useful while in fort mode +- `tag/legends`: Tools that are useful while in legends mode +- `tag/items`: Tools that create or modify in-game items +- `tag/units`: Tools that create or modify units +- `tag/jobs`: Tools that create or modify jobs +- `tag/labors`: Tools that deal with labor assignment +- `tag/auto`: Tools that automatically manage some aspect of your fortress +- `tag/map`: Map modification +- `tag/system`: Tools related to working with DFHack commands or the core DFHack library +- `tag/productivity`: Tools that help you do things that you could do manually, but using the tool is better and faster +- `tag/animals`: Tools that help you manage animals +- `tag/fix`: Tools that fix specific bugs +- `tag/inspection`: Tools that let you inspect game data +- `tag/buildings`: Tools that help you work wtih placing or configuring buildings and furniture +- `tag/quickfort`: Tools that are involved in creating and playing back blueprints +- `tag/dev`: Tools useful for develpers and modders diff --git a/docs/builtins/alias.rst b/docs/builtins/alias.rst index 6de550875..234f8ae4a 100644 --- a/docs/builtins/alias.rst +++ b/docs/builtins/alias.rst @@ -1,7 +1,7 @@ alias ===== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`alias` :index:`Configure helper aliases for other DFHack commands. diff --git a/docs/builtins/cls.rst b/docs/builtins/cls.rst index 5c1189951..2f5387c7f 100644 --- a/docs/builtins/cls.rst +++ b/docs/builtins/cls.rst @@ -1,7 +1,7 @@ cls === -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`cls` :index:`Clear the terminal screen. ` Can also diff --git a/docs/builtins/devel/dump-rpc.rst b/docs/builtins/devel/dump-rpc.rst index 05f1d4e9e..2f798c79e 100644 --- a/docs/builtins/devel/dump-rpc.rst +++ b/docs/builtins/devel/dump-rpc.rst @@ -1,7 +1,7 @@ devel/dump-rpc ============== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`devel/dump-rpc` :index:`Write RPC endpoint information to the specified file. diff --git a/docs/builtins/die.rst b/docs/builtins/die.rst index c2ea083fd..df53c94bd 100644 --- a/docs/builtins/die.rst +++ b/docs/builtins/die.rst @@ -1,7 +1,7 @@ die === -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`die` :index:`Instantly exit DF without saving. diff --git a/docs/builtins/disable.rst b/docs/builtins/disable.rst index 1e2336545..d17706486 100644 --- a/docs/builtins/disable.rst +++ b/docs/builtins/disable.rst @@ -1,7 +1,7 @@ disable ======= -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`disable` :index:`Deactivate a DFHack tool that has some persistent effect. diff --git a/docs/builtins/enable.rst b/docs/builtins/enable.rst index 4630c2004..1a714f602 100644 --- a/docs/builtins/enable.rst +++ b/docs/builtins/enable.rst @@ -1,7 +1,7 @@ enable ====== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`enable` :index:`Activate a DFHack tool that has some persistent effect. diff --git a/docs/builtins/fpause.rst b/docs/builtins/fpause.rst index 927206e8d..f40f6d55c 100644 --- a/docs/builtins/fpause.rst +++ b/docs/builtins/fpause.rst @@ -1,7 +1,7 @@ fpause ====== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`fpause` :index:`Forces DF to pause. ` This is useful when diff --git a/docs/builtins/help.rst b/docs/builtins/help.rst index 6249bdbe9..290d62e4c 100644 --- a/docs/builtins/help.rst +++ b/docs/builtins/help.rst @@ -1,7 +1,7 @@ help ==== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`help` :index:`Display help about a command or plugin. diff --git a/docs/builtins/hide.rst b/docs/builtins/hide.rst index 3c79f9ff0..e4f91abaa 100644 --- a/docs/builtins/hide.rst +++ b/docs/builtins/hide.rst @@ -1,7 +1,7 @@ hide ==== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`hide` :index:`Hide the DFHack terminal window. diff --git a/docs/builtins/keybinding.rst b/docs/builtins/keybinding.rst index 27ab86da6..2ad9e1542 100644 --- a/docs/builtins/keybinding.rst +++ b/docs/builtins/keybinding.rst @@ -1,7 +1,7 @@ keybinding ========== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`keybinding` :index:`Create hotkeys that will run DFHack commands. diff --git a/docs/builtins/kill-lua.rst b/docs/builtins/kill-lua.rst index 3f4bbf3c4..4b93b8734 100644 --- a/docs/builtins/kill-lua.rst +++ b/docs/builtins/kill-lua.rst @@ -1,7 +1,7 @@ kill-lua ======== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`kill-lua` :index:`Gracefully stops any currently-running Lua scripts. diff --git a/docs/builtins/load.rst b/docs/builtins/load.rst index 22e8067f3..8e51847dd 100644 --- a/docs/builtins/load.rst +++ b/docs/builtins/load.rst @@ -1,7 +1,7 @@ load ==== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`load` :index:`Load and register a plugin library. diff --git a/docs/builtins/ls.rst b/docs/builtins/ls.rst index 775ccfc63..e68b740c7 100644 --- a/docs/builtins/ls.rst +++ b/docs/builtins/ls.rst @@ -1,7 +1,7 @@ ls == -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`ls` :index:`List available DFHack commands. ` diff --git a/docs/builtins/plug.rst b/docs/builtins/plug.rst index 7dbbf0115..47efe66b3 100644 --- a/docs/builtins/plug.rst +++ b/docs/builtins/plug.rst @@ -1,7 +1,7 @@ plug ==== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`plug` :index:`List available plugins and whether they are enabled. diff --git a/docs/builtins/reload.rst b/docs/builtins/reload.rst index a5ab60859..cde113ac8 100644 --- a/docs/builtins/reload.rst +++ b/docs/builtins/reload.rst @@ -1,7 +1,7 @@ reload ====== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`reload` :index:`Reload a loaded plugin. ` Developers diff --git a/docs/builtins/sc-script.rst b/docs/builtins/sc-script.rst index feabe4c6f..bcedb610e 100644 --- a/docs/builtins/sc-script.rst +++ b/docs/builtins/sc-script.rst @@ -1,7 +1,7 @@ sc-script ========= -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`sc-script` :index:`Run commands when game state changes occur. diff --git a/docs/builtins/script.rst b/docs/builtins/script.rst index b83f19171..ef1aab64a 100644 --- a/docs/builtins/script.rst +++ b/docs/builtins/script.rst @@ -1,7 +1,7 @@ script ====== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`script` :index:`Execute a batch file of DFHack commands. diff --git a/docs/builtins/show.rst b/docs/builtins/show.rst index e337bbbb6..b22d4bab9 100644 --- a/docs/builtins/show.rst +++ b/docs/builtins/show.rst @@ -1,7 +1,7 @@ show ==== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`show` :index:`Unhides the DFHack terminal window. diff --git a/docs/builtins/tags.rst b/docs/builtins/tags.rst index cc5c86a4b..b601a73a1 100644 --- a/docs/builtins/tags.rst +++ b/docs/builtins/tags.rst @@ -1,7 +1,7 @@ tags ==== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`tags` :index:`List the strings that DFHack tools can be tagged with. diff --git a/docs/builtins/type.rst b/docs/builtins/type.rst index 33ef7562f..755548da9 100644 --- a/docs/builtins/type.rst +++ b/docs/builtins/type.rst @@ -1,7 +1,7 @@ type ==== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`type` :index:`Describe how a command is implemented. diff --git a/docs/builtins/unload.rst b/docs/builtins/unload.rst index 3d1575c20..1fc8c311e 100644 --- a/docs/builtins/unload.rst +++ b/docs/builtins/unload.rst @@ -1,7 +1,7 @@ unload ====== -Tags: :dfhack-tag:`system` +Tags: `tag/system` :dfhack-keybind:`unload` :index:`Unload a plugin from memory. ` From f9c8d36e0357b2f6f48eba1db9fb87bedf4d44d8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 15:21:16 -0700 Subject: [PATCH 283/854] mark the generated index as an orphan --- conf.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/conf.py b/conf.py index 854693ad0..b36e8aaad 100644 --- a/conf.py +++ b/conf.py @@ -135,12 +135,11 @@ def get_open_mode(): def generate_tag_indices(): os.makedirs('docs/tags', mode=0o755, exist_ok=True) with open('docs/tags/index.rst', get_open_mode()) as topidx: - #topidx.write(':orphan:\n\n') + topidx.write(':orphan:\n\n') for tag_tuple in get_tags(): tag = tag_tuple[0] with open(('docs/tags/{name}.rst').format(name=tag), get_open_mode()) as tagidx: - #tagidx.write(':orphan:\n\n') tagidx.write('TODO: add links to the tools that have this tag') topidx.write(('.. _tag/{name}:\n\n').format(name=tag)) topidx.write(('{name}\n').format(name=tag)) From 47af2ef396c175beedf1bf90ea5c2d737a719494 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 15:28:09 -0700 Subject: [PATCH 284/854] exclude the generated included tags/ files --- conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conf.py b/conf.py index b36e8aaad..b2c5b3008 100644 --- a/conf.py +++ b/conf.py @@ -295,6 +295,7 @@ exclude_patterns = [ 'build*', 'depends/*', 'docs/html/*', + 'docs/tags/*', 'docs/text/*', 'docs/builtins/*', 'docs/plugins/*', From 93923e12f4a74c2dc0dc7e220e65569fe4266901 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 15:31:58 -0700 Subject: [PATCH 285/854] no need to mark the included file as an orphan --- conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/conf.py b/conf.py index b2c5b3008..814c7c2cb 100644 --- a/conf.py +++ b/conf.py @@ -135,7 +135,6 @@ def get_open_mode(): def generate_tag_indices(): os.makedirs('docs/tags', mode=0o755, exist_ok=True) with open('docs/tags/index.rst', get_open_mode()) as topidx: - topidx.write(':orphan:\n\n') for tag_tuple in get_tags(): tag = tag_tuple[0] with open(('docs/tags/{name}.rst').format(name=tag), From 19a49059337b56bfcea47e4c4f3a364c91ebee79 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 16:42:36 -0700 Subject: [PATCH 286/854] update docs for "c" plugins --- docs/plugins/changeitem.rst | 5 ++- docs/plugins/changelayer.rst | 9 +++-- docs/plugins/changevein.rst | 9 +++-- docs/plugins/clean.rst | 37 ------------------ docs/plugins/cleanconst.rst | 11 +++--- docs/plugins/cleaners.rst | 46 +++++++++++++++++++++++ docs/plugins/cleanowned.rst | 11 +++--- docs/plugins/command-prompt.rst | 19 ++++++---- docs/plugins/confirm.rst | 22 +++++++---- docs/plugins/createitem.rst | 66 +++++++++++++++++---------------- docs/plugins/cromulate.rst | 14 ++++--- docs/plugins/cursecheck.rst | 66 ++++++++++++++++++++------------- docs/plugins/spotclean.rst | 14 ------- plugins/changelayer.cpp | 2 +- plugins/changevein.cpp | 2 +- plugins/cleaners.cpp | 4 +- plugins/command-prompt.cpp | 9 ++--- plugins/confirm.cpp | 9 +---- plugins/createitem.cpp | 25 ++----------- plugins/cromulate.cpp | 4 +- plugins/cursecheck.cpp | 5 ++- 21 files changed, 199 insertions(+), 190 deletions(-) delete mode 100644 docs/plugins/clean.rst create mode 100644 docs/plugins/cleaners.rst delete mode 100644 docs/plugins/spotclean.rst diff --git a/docs/plugins/changeitem.rst b/docs/plugins/changeitem.rst index 0a841511c..5dd537a60 100644 --- a/docs/plugins/changeitem.rst +++ b/docs/plugins/changeitem.rst @@ -4,8 +4,9 @@ changeitem Tags: :dfhack-keybind: -Allows changing item material and base quality. By default, a change is only -allowed if the existing and desired item materials are of the same subtype +:index:`Change item material and base quality. +` By default, a change is +only allowed if the existing and desired item materials are of the same subtype (for example wood -> wood, stone -> stone, etc). But since some transformations work pretty well and may be desired you can override this with ``force``. Note that forced changes can possibly result in items that crafters and haulers diff --git a/docs/plugins/changelayer.rst b/docs/plugins/changelayer.rst index 535409e90..76d631cdf 100644 --- a/docs/plugins/changelayer.rst +++ b/docs/plugins/changelayer.rst @@ -4,10 +4,11 @@ changelayer Tags: :dfhack-keybind: -Change the material of an entire geology layer. Note that one layer can stretch -across many z-levels, and changes to the geology layer will affect all -surrounding regions, not just your embark! Mineral veins and gem clusters will -not be affected. Use `changevein` if you want to modify those. +:index:`Change the material of an entire geology layer. +` Note that one +layer can stretch across many z-levels, and changes to the geology layer will +affect all surrounding regions, not just your embark! Mineral veins and gem +clusters will not be affected. Use `changevein` if you want to modify those. tl;dr: You will end up with changing large areas in one go, especially if you use it in lower z levels. Use this command with care! diff --git a/docs/plugins/changevein.rst b/docs/plugins/changevein.rst index f4871772e..7de94a3f0 100644 --- a/docs/plugins/changevein.rst +++ b/docs/plugins/changevein.rst @@ -4,10 +4,11 @@ changevein Tags: :dfhack-keybind: -Changes the material of a mineral inclusion. You can change it to any incorganic -material RAW id. Note that this command only affects tiles within the current -16x16 block - for large veins and clusters, you will need to use this command -multiple times. +:index:`Change the material of a mineral inclusion. +` You can change it to +any incorganic material RAW id. Note that this command only affects tiles within +the current 16x16 block - for large veins and clusters, you will need to use +this command multiple times. You can use the `probe` command to discover the material RAW ids for existing veins that you want to duplicate. diff --git a/docs/plugins/clean.rst b/docs/plugins/clean.rst deleted file mode 100644 index 54421ce91..000000000 --- a/docs/plugins/clean.rst +++ /dev/null @@ -1,37 +0,0 @@ -clean -===== - -Tags: -:dfhack-keybind: - -Removes contaminants from tiles, items, and units. More specifically, it -cleans all the splatter that get scattered all over the map and that clings to -your items and units. In an old fortress, this can significantly reduce FPS lag. -It can also spoil your !!FUN!!, so think before you use it. - -Usage:: - - clean all|map|items|units|plants [] - -By default, cleaning the map leaves mud and snow alone. Note that cleaning units -includes hostiles, and that cleaning items removes poisons from weapons. - -Options -------- - -When cleaning the map, you can specify extra options for extra cleaning: - -- ``mud`` - Also remove mud. -- ``item`` - Also remove item spatter, like fallen leaves and flowers. -- ``snow`` - Also remove snow coverings. - -Examples --------- - -- ``clean all`` - Clean everything that can be cleaned (except mud and snow). -- ``clean all mud item snow`` - Removes all spatter, including mud, leaves, and snow from map tiles. diff --git a/docs/plugins/cleanconst.rst b/docs/plugins/cleanconst.rst index c34bac6bf..286069b63 100644 --- a/docs/plugins/cleanconst.rst +++ b/docs/plugins/cleanconst.rst @@ -2,12 +2,13 @@ cleanconst ========== Tags: -:dfhack-keybind: +:dfhack-keybind:`cleanconst` -Cleans up construction materials. This tool alters all constructions on the map -so that they spawn their building component when they are disassembled, allowing -their actual build items to be safely deleted. This can improve FPS when you -have many constructions on the map. +:index:`Cleans up construction materials. +` This tool alters all +constructions on the map so that they spawn their building component when they +are disassembled, allowing their actual build items to be safely deleted. This +can improve FPS when you have many constructions on the map. Usage:: diff --git a/docs/plugins/cleaners.rst b/docs/plugins/cleaners.rst new file mode 100644 index 000000000..b290a73a4 --- /dev/null +++ b/docs/plugins/cleaners.rst @@ -0,0 +1,46 @@ +cleaners +======== + +Tags: +:dfhack-keybind:`clean` +:dfhack-keybind:`spotclean` + +:index:`Removes contaminants from tiles, items, and units. +` More specifically, +it cleans all the splatter that get scattered all over the map and that clings +to your items and units. In an old fortress, this can significantly reduce FPS +lag. It can also spoil your !!FUN!!, so think before you use it. + +Usage:: + + clean all|map|items|units|plants [] + spotclean + +By default, cleaning the map leaves mud and snow alone. Note that cleaning units +includes hostiles, and that cleaning items removes poisons from weapons. + +``spotclean`` works like ``clean map snow mud``, +:index:`removing all contaminants from the tile under the cursor. +` This is ideal +if you just want to clean a specific tile but don't want the `clean` command to +remove all the glorious blood from your entranceway. + +Options +------- + +When cleaning the map, you can specify extra options for extra cleaning: + +- ``mud`` + Also remove mud. +- ``item`` + Also remove item spatter, like fallen leaves and flowers. +- ``snow`` + Also remove snow coverings. + +Examples +-------- + +- ``clean all`` + Clean everything that can be cleaned (except mud and snow). +- ``clean all mud item snow`` + Removes all spatter, including mud, leaves, and snow from map tiles. diff --git a/docs/plugins/cleanowned.rst b/docs/plugins/cleanowned.rst index 27e4806a2..e827989c9 100644 --- a/docs/plugins/cleanowned.rst +++ b/docs/plugins/cleanowned.rst @@ -2,12 +2,13 @@ cleanowned ========== Tags: -:dfhack-keybind: +:dfhack-keybind:`cleanowned` -Confiscates and dumps garbage owned by dwarves. This tool gets dwarves to give -up ownership of scattered items and items with heavy wear and then marks those -items for dumping. Now you can finally get your dwarves to give up their rotten -food and tattered loincloths and go get new ones! +:index:`Confiscates and dumps garbage owned by dwarves. +` This tool gets +dwarves to give up ownership of scattered items and items with heavy wear and +then marks those items for dumping. Now you can finally get your dwarves to give +up their rotten food and tattered loincloths and go get new ones! Usage:: diff --git a/docs/plugins/command-prompt.rst b/docs/plugins/command-prompt.rst index 061b405ef..6e3d2a829 100644 --- a/docs/plugins/command-prompt.rst +++ b/docs/plugins/command-prompt.rst @@ -1,16 +1,21 @@ command-prompt ============== -An in-game DFHack terminal, where you can enter other commands. +Tags: :dfhack-keybind:`command-prompt` -Usage: ``command-prompt [entry]`` +:index:`An in-game DFHack terminal, where you can enter other commands. +` -If called with an entry, it starts with that text filled in. -Most useful for developers, who can set a keybinding to open -a laungage interpreter for lua or Ruby by starting with the -`:lua ` or `:rb ` commands. +Usage:: -Otherwise somewhat similar to `gui/quickcmd`. + command-prompt [entry] + +If called with parameters, it starts with that text in the command edit area. +This is most useful for developers, who can set a keybinding to open a laungage +interpreter for lua or Ruby by starting with the `:lua ` or `:rb ` +portions of the command already filled in. + +Also see `gui/quickcmd` for a different take on running commands from the UI. .. image:: ../images/command-prompt.png diff --git a/docs/plugins/confirm.rst b/docs/plugins/confirm.rst index c7fb89bb0..25cc04956 100644 --- a/docs/plugins/confirm.rst +++ b/docs/plugins/confirm.rst @@ -1,12 +1,20 @@ confirm ======= -Implements several confirmation dialogs for potentially destructive actions -(for example, seizing goods from traders or deleting hauling routes). + +Tags: +:dfhack-keybind:`confirm` + +:index:`Adds confirmation dialogs for destructive actions. +` Now you can get +the chance to avoid seizing goods from traders or deleting a hauling route in +case you hit the key accidentally. Usage: -:enable confirm: Enable all confirmations; alias ``confirm enable all``. - Replace with ``disable`` to disable. -:confirm help: List available confirmation dialogues. -:confirm enable option1 [option2...]: - Enable (or disable) specific confirmation dialogues. +- ``enable confirm``, ``confirm enable all`` + Enable all confirmation options. Replace with ``disable`` to disable all. +- ``confirm enable option1 [option2...]`` + Enable (or ``disable``) specific confirmation dialogs. + +When run without parameters, ``confirm`` will report which confirmation dialogs +are currently enabled. diff --git a/docs/plugins/createitem.rst b/docs/plugins/createitem.rst index f37154f80..13ecc2e58 100644 --- a/docs/plugins/createitem.rst +++ b/docs/plugins/createitem.rst @@ -1,48 +1,52 @@ createitem ========== -Allows creating new items of arbitrary types and made of arbitrary materials. A -unit must be selected in-game to use this command. By default, items created are -spawned at the feet of the selected unit. -Specify the item and material information as you would indicate them in -custom reaction raws, with the following differences: +Tags: +:dfhack-keybind:`createitem` + +:index:`Create arbitrary items. ` You can +create new items of any type and made of any material. A unit must be selected +in-game to use this command. By default, items created are spawned at the feet +of the selected unit. + +Specify the item and material information as you would indicate them in custom +reaction raws, with the following differences: * Separate the item and material with a space rather than a colon * If the item has no subtype, the ``:NONE`` can be omitted -* If the item is ``REMAINS``, ``FISH``, ``FISH_RAW``, ``VERMIN``, ``PET``, or ``EGG``, - specify a ``CREATURE:CASTE`` pair instead of a material token. +* If the item is ``REMAINS``, ``FISH``, ``FISH_RAW``, ``VERMIN``, ``PET``, or + ``EGG``, then specify a ``CREATURE:CASTE`` pair instead of a material token. * If the item is a ``PLANT_GROWTH``, specify a ``PLANT_ID:GROWTH_ID`` pair instead of a material token. Corpses, body parts, and prepared meals cannot be created using this tool. -To obtain the item and material tokens of an existing item, run -``createitem inspect``. Its output can be passed directly as arguments to -``createitem`` to create new matching items, as long as the item type is -supported. - -Examples: - -* Create 2 pairs of steel gauntlets:: +Usage: - createitem GLOVES:ITEM_GLOVES_GAUNTLETS INORGANIC:STEEL 2 +- ``createitem []`` + Create copies (default is 1) of the specified item made out of the + specified material. +- ``createitem inspect`` + Obtain the item and material tokens of an existing item. Its output can be + used directly as arguments to ``createitem`` to create new matching items + (as long as the item type is supported). +- ``createitem floor|item|building`` + Subsequently created items will be placed on the floor beneath the selected + unit's, inside the selected item, or as part of the selected building. -* Create tower-cap logs:: +.. note:: - createitem WOOD PLANT_MAT:TOWER_CAP:WOOD + ``createitem building`` is good for loading traps, but if you use it with + workshops, you will have to deconstruct the workshop to access the item. -* Create bilberries:: - - createitem PLANT_GROWTH BILBERRY:FRUIT - -For more examples, :wiki:`see this wiki page `. - -To change where new items are placed, first run the command with a -destination type while an appropriate destination is selected. +Examples: -Options: +- ``createitem GLOVES:ITEM_GLOVES_GAUNTLETS INORGANIC:STEEL 2`` + Create 2 pairs of steel gauntlets (that is, 2 left gauntlets and 2 right + gauntlets). +- ``createitem WOOD PLANT_MAT:TOWER_CAP:WOOD 100`` + Create 100 tower-cap logs. +- ``createitem PLANT_GROWTH BILBERRY:FRUIT`` + Create a single bilberry. -:floor: Subsequent items will be placed on the floor beneath the selected unit's feet. -:item: Subsequent items will be stored inside the currently selected item. -:building: Subsequent items will become part of the currently selected building. - Good for loading traps; do not use with workshops (or deconstruct to use the item). +For more examples, :wiki:`the wiki `. diff --git a/docs/plugins/cromulate.rst b/docs/plugins/cromulate.rst index ca07ae0ca..b025fe877 100644 --- a/docs/plugins/cromulate.rst +++ b/docs/plugins/cromulate.rst @@ -1,12 +1,14 @@ cromulate ========= -Tags: productivity, unit, adventure +Tags: `tag/productivity`, `tag/unit`, `tag/adventure` :dfhack-keybind:`cromulate` -Collects all widgets into a frobozz electric cromufiler. You might want to do -this if you discover that your widgets have become decromulated. It is safe to -run this command periodically even if you are unsure if that's the case. +:index:`Collects all widgets into a frobozz electric cromufiler. +` You might +want to do this if you discover that your widgets have become decromulated. It +is safe to run this command periodically even if you are unsure if that's the +case. Usage:: @@ -18,9 +20,9 @@ collect those under the cursor. Options: -:``-d``, ``--destroy``: +- ``-d``, ``--destroy`` Destroy the widgets instead of collecting them into the cromufiler. -:``-q``, ``--quiet``: +- ``-q``, ``--quiet`` Don't display any informational output. Errors will still be printed to the console. diff --git a/docs/plugins/cursecheck.rst b/docs/plugins/cursecheck.rst index 1bf1803fa..79da6d621 100644 --- a/docs/plugins/cursecheck.rst +++ b/docs/plugins/cursecheck.rst @@ -1,42 +1,56 @@ cursecheck ========== -Checks a single map tile or the whole map/world for cursed creatures (ghosts, -vampires, necromancers, werebeasts, zombies). -With an active in-game cursor only the selected tile will be observed. -Without a cursor the whole map will be checked. +Tags: +:dfhack-keybind:`cursecheck` -By default cursed creatures will be only counted in case you just want to find -out if you have any of them running around in your fort. Dead and passive -creatures (ghosts who were put to rest, killed vampires, ...) are ignored. -Undead skeletons, corpses, bodyparts and the like are all thrown into the curse -category "zombie". Anonymous zombies and resurrected body parts will show -as "unnamed creature". +:index:`Check for cursed creatures. ` +This command checks a single map tile (or the whole map/world) for cursed +creatures (ghosts, vampires, necromancers, werebeasts, zombies, etc.). + +With an active in-game cursor, only the selected tile will be checked. Without a +cursor, the whole map will be checked. + +By default, you will just see the count of cursed creatures in case you just +want to find out if you have any of them running around in your fort. Dead and +passive creatures (ghosts who were put to rest, killed vampires, etc.) are +ignored. Undead skeletons, corpses, bodyparts and the like are all thrown into +the curse category "zombie". Anonymous zombies and resurrected body parts will +show as "unnamed creature". + +Usage:: + + cursecheck [] Options: -:detail: Print full name, date of birth, date of curse and some status - info (some vampires might use fake identities in-game, though). -:ids: Print the creature and race IDs. -:nick: Set the type of curse as nickname (does not always show up - in-game, some vamps don't like nicknames). -:all: Include dead and passive cursed creatures (can result in a quite - long list after having FUN with necromancers). -:verbose: Print all curse tags (if you really want to know it all). +- ``detail`` + Print full name, date of birth, date of curse, and some status info (some + vampires might use fake identities in-game, though). +- ``nick`` + Set the type of curse as nickname (does not always show up in-game; some + vamps don't like nicknames). +- ``ids`` + Print the creature and race IDs. +- ``all`` + Include dead and passive cursed creatures (this can result in quite a long + list after having !!FUN!! with necromancers). +- ``verbose`` + Print all curse tags (if you really want to know it all). Examples: -``cursecheck detail all`` - Give detailed info about all cursed creatures including deceased ones (no - in-game cursor). -``cursecheck nick`` - Give a nickname all living/active cursed creatures on the map(no in-game - cursor). +- ``cursecheck`` + Display a count of cursed creatures on the map (or under the cursor). +- ``cursecheck detail all`` + Give detailed info about all cursed creatures including deceased ones. +- ``cursecheck nick`` + Give a nickname all living/active cursed creatures. .. note:: If you do a full search (with the option "all") former ghosts will show up with the cursetype "unknown" because their ghostly flag is not set. - Please report any living/active creatures with cursetype "unknown" - - this is most likely with mods which introduce new types of curses. + If you see any living/active creatures with a cursetype of "unknown", then + it is most likely a new type of curse introduced by a mod. diff --git a/docs/plugins/spotclean.rst b/docs/plugins/spotclean.rst deleted file mode 100644 index dcad047a4..000000000 --- a/docs/plugins/spotclean.rst +++ /dev/null @@ -1,14 +0,0 @@ -spotclean -========= - -Tags: -:dfhack-keybind: - -Cleans a map tile of contaminants and spatter. It works like -``clean map snow mud``, but only for the tile under the cursor. Ideal if you -just want to clean a specific tile but don't want the `clean` command to remove -all the glorious blood from your entranceway. - -Usage:: - - spotclean diff --git a/plugins/changelayer.cpp b/plugins/changelayer.cpp index 347f29b95..7520ac932 100644 --- a/plugins/changelayer.cpp +++ b/plugins/changelayer.cpp @@ -37,7 +37,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("changevein", - "Changes the material of a mineral inclusion.", + "Change the material of a mineral inclusion.", df_changevein)); return CR_OK; } diff --git a/plugins/cleaners.cpp b/plugins/cleaners.cpp index 7389488f1..00075e9d7 100644 --- a/plugins/cleaners.cpp +++ b/plugins/cleaners.cpp @@ -242,11 +242,11 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "command-prompt", "Shows a command prompt on window.", - show_prompt, hotkey_allow_all, - "command-prompt [entry] - shows a cmd prompt in df window." - " Entry is used for default prefix (e.g. ':lua')" - )); + "command-prompt", + "An in-game DFHack terminal.", + show_prompt, + hotkey_allow_all)); return CR_OK; } diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index 32da4e1f8..5cef6376f 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -547,13 +547,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("createitem", "Create arbitrary items.", df_createitem, false, - "Syntax: createitem [count]\n" - " - Item token for what you wish to create, as specified in custom\n" - " reactions. If the item has no subtype, omit the :NONE.\n" - " - The material you want the item to be made of, as specified\n" - " in custom reactions. For REMAINS, FISH, FISH_RAW, VERMIN,\n" - " PET, and EGG, replace this with a creature ID and caste.\n" - " For PLANT_GROWTH, replace this with a plant ID and growth ID.\n" - " [count] - How many of the item you wish to create.\n" - "\n" - "To obtain the item and material of an existing item, run \n" - "'createitem inspect' with that item selected in-game.\n" - "\n" - "To use this command, you must select which unit will create the items.\n" - "By default, items created will be placed at that unit's feet.\n" - "To change this, run 'createitem '.\n" - "Valid destinations:\n" - "* floor - Place items on floor beneath maker's feet.\n" - "* item - Place items inside selected container.\n" - "* building - Place items inside selected building.\n" - )); + commands.push_back( + PluginCommand("createitem", + "Create arbitrary items.", + df_createitem)); return CR_OK; } diff --git a/plugins/cromulate.cpp b/plugins/cromulate.cpp index 9f7fdb9f9..7ed9ed115 100644 --- a/plugins/cromulate.cpp +++ b/plugins/cromulate.cpp @@ -13,9 +13,7 @@ command_result cromulate (color_ostream &out, std::vector & parame DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("cromulate", "in-cpp plugin short desc", //to use one line in the ``[DFHack]# ls`` output - cromulate, - false, - "in-cpp plugin long help")); + cromulate)); return CR_OK; } diff --git a/plugins/cursecheck.cpp b/plugins/cursecheck.cpp index d20c645ab..007c81228 100644 --- a/plugins/cursecheck.cpp +++ b/plugins/cursecheck.cpp @@ -79,9 +79,10 @@ command_result cursecheck (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("cursecheck", + commands.push_back(PluginCommand( + "cursecheck", "Check for cursed creatures (undead, necromancers...)", - cursecheck, false )); + cursecheck)); return CR_OK; } From 65b3ce6e9698abbb2e634f72fcf15ab926f84ddc Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 17:45:23 -0700 Subject: [PATCH 287/854] remove unattached docs and plugins --- docs/plugins/alltraffic.rst | 11 -- docs/plugins/autobutcher.rst | 91 ---------- docs/plugins/autonestbox.rst | 19 --- docs/plugins/digcircle.rst | 35 ---- docs/plugins/digexp.rst | 31 ---- docs/plugins/digtype.rst | 19 --- plugins/dfstream.cpp | 316 ----------------------------------- 7 files changed, 522 deletions(-) delete mode 100644 docs/plugins/alltraffic.rst delete mode 100644 docs/plugins/autobutcher.rst delete mode 100644 docs/plugins/autonestbox.rst delete mode 100644 docs/plugins/digcircle.rst delete mode 100644 docs/plugins/digexp.rst delete mode 100644 docs/plugins/digtype.rst delete mode 100644 plugins/dfstream.cpp diff --git a/docs/plugins/alltraffic.rst b/docs/plugins/alltraffic.rst deleted file mode 100644 index 1e9116954..000000000 --- a/docs/plugins/alltraffic.rst +++ /dev/null @@ -1,11 +0,0 @@ -alltraffic -========== -Set traffic designations for every single tile of the map - useful for resetting -traffic designations. See also `filltraffic`, `restrictice`, and `restrictliquids`. - -Options: - -:H: High Traffic -:N: Normal Traffic -:L: Low Traffic -:R: Restricted Traffic diff --git a/docs/plugins/autobutcher.rst b/docs/plugins/autobutcher.rst deleted file mode 100644 index d761cbdbe..000000000 --- a/docs/plugins/autobutcher.rst +++ /dev/null @@ -1,91 +0,0 @@ -autobutcher -=========== -Assigns lifestock for slaughter once it reaches a specific count. Requires that -you add the target race(s) to a watch list. Only tame units will be processed. - -Units will be ignored if they are: - -* Nicknamed (for custom protection; you can use the `rename` ``unit`` tool - individually, or `zone` ``nick`` for groups) -* Caged, if and only if the cage is defined as a room (to protect zoos) -* Trained for war or hunting - -Creatures who will not reproduce (because they're not interested in the -opposite sex or have been gelded) will be butchered before those who will. -Older adults and younger children will be butchered first if the population -is above the target (default 1 male, 5 female kids and adults). Note that -you may need to set a target above 1 to have a reliable breeding population -due to asexuality etc. See `fix-ster` if this is a problem. - -Options: - -:example: Print some usage examples. -:start: Start running every X frames (df simulation ticks). - Default: X=6000, which would be every 60 seconds at 100fps. -:stop: Stop running automatically. -:sleep : Changes the timer to sleep X frames between runs. -:watch R: Start watching a race. R can be a valid race RAW id (ALPACA, - BIRD_TURKEY, etc) or a list of ids seperated by spaces or - the keyword 'all' which affects all races on your current - watchlist. -:unwatch R: Stop watching race(s). The current target settings will be - remembered. R can be a list of ids or the keyword 'all'. -:forget R: Stop watching race(s) and forget it's/their target settings. - R can be a list of ids or the keyword 'all'. -:autowatch: Automatically adds all new races (animals you buy from merchants, - tame yourself or get from migrants) to the watch list using - default target count. -:noautowatch: Stop auto-adding new races to the watchlist. -:list: Print the current status and watchlist. -:list_export: Print the commands needed to set up status and watchlist, - which can be used to import them to another save (see notes). -:target : - Set target count for specified race(s). The first four arguments - are the number of female and male kids, and female and male adults. - R can be a list of spceies ids, or the keyword ``all`` or ``new``. - ``R = 'all'``: change target count for all races on watchlist - and set the new default for the future. ``R = 'new'``: don't touch - current settings on the watchlist, only set the new default - for future entries. -:list_export: Print the commands required to rebuild your current settings. - -.. note:: - - Settings and watchlist are stored in the savegame, so that you can have - different settings for each save. If you want to copy your watchlist to - another savegame you must export the commands required to recreate your settings. - - To export, open an external terminal in the DF directory, and run - ``dfhack-run autobutcher list_export > filename.txt``. To import, load your - new save and run ``script filename.txt`` in the DFHack terminal. - - -Examples: - -You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, -1 male) of the race alpaca. Once the kids grow up the oldest adults will get -slaughtered. Excess kids will get slaughtered starting with the youngest -to allow that the older ones grow into adults. Any unnamed cats will -be slaughtered as soon as possible. :: - - autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY - autobutcher target 0 0 0 0 CAT - autobutcher watch ALPACA BIRD_TURKEY CAT - autobutcher start - -Automatically put all new races onto the watchlist and mark unnamed tame units -for slaughter as soon as they arrive in your fort. Settings already made -for specific races will be left untouched. :: - - autobutcher target 0 0 0 0 new - autobutcher autowatch - autobutcher start - -Stop watching the races alpaca and cat, but remember the target count -settings so that you can use 'unwatch' without the need to enter the -values again. Note: 'autobutcher unwatch all' works, but only makes sense -if you want to keep the plugin running with the 'autowatch' feature or manually -add some new races with 'watch'. If you simply want to stop it completely use -'autobutcher stop' instead. :: - - autobutcher unwatch ALPACA CAT diff --git a/docs/plugins/autonestbox.rst b/docs/plugins/autonestbox.rst deleted file mode 100644 index f19cad42c..000000000 --- a/docs/plugins/autonestbox.rst +++ /dev/null @@ -1,19 +0,0 @@ -autonestbox -=========== -Assigns unpastured female egg-layers to nestbox zones. Requires that you create -pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox -must be in the top left corner. Only 1 unit will be assigned per pen, regardless -of the size. The age of the units is currently not checked, most birds grow up -quite fast. Egglayers who are also grazers will be ignored, since confining them -to a 1x1 pasture is not a good idea. Only tame and domesticated own units are -processed since pasturing half-trained wild egglayers could destroy your neat -nestbox zones when they revert to wild. When called without options autonestbox -will instantly run once. - -Options: - -:start: Start running every X frames (df simulation ticks). - Default: X=6000, which would be every 60 seconds at 100fps. -:stop: Stop running automatically. -:sleep: Must be followed by number X. Changes the timer to sleep X - frames between runs. diff --git a/docs/plugins/digcircle.rst b/docs/plugins/digcircle.rst deleted file mode 100644 index fc31b65df..000000000 --- a/docs/plugins/digcircle.rst +++ /dev/null @@ -1,35 +0,0 @@ -digcircle -========= -A command for easy designation of filled and hollow circles. -It has several types of options. - -Shape: - -:hollow: Set the circle to hollow (default) -:filled: Set the circle to filled -:#: Diameter in tiles (default = 0, does nothing) - -Action: - -:set: Set designation (default) -:unset: Unset current designation -:invert: Invert designations already present - -Designation types: - -:dig: Normal digging designation (default) -:ramp: Ramp digging -:ustair: Staircase up -:dstair: Staircase down -:xstair: Staircase up/down -:chan: Dig channel - -After you have set the options, the command called with no options -repeats with the last selected parameters. - -Examples: - -``digcircle filled 3`` - Dig a filled circle with diameter = 3. -``digcircle`` - Do it again. diff --git a/docs/plugins/digexp.rst b/docs/plugins/digexp.rst deleted file mode 100644 index e4412d051..000000000 --- a/docs/plugins/digexp.rst +++ /dev/null @@ -1,31 +0,0 @@ -digexp -====== -This command is for :wiki:`exploratory mining `. - -There are two variables that can be set: pattern and filter. - -Patterns: - -:diag5: diagonals separated by 5 tiles -:diag5r: diag5 rotated 90 degrees -:ladder: A 'ladder' pattern -:ladderr: ladder rotated 90 degrees -:clear: Just remove all dig designations -:cross: A cross, exactly in the middle of the map. - -Filters: - -:all: designate whole z-level -:hidden: designate only hidden tiles of z-level (default) -:designated: Take current designation and apply pattern to it. - -After you have a pattern set, you can use ``expdig`` to apply it again. - -Examples: - -``expdig diag5 hidden`` - Designate the diagonal 5 patter over all hidden tiles -``expdig`` - Apply last used pattern and filter -``expdig ladder designated`` - Take current designations and replace them with the ladder pattern diff --git a/docs/plugins/digtype.rst b/docs/plugins/digtype.rst deleted file mode 100644 index 1c2d49eaa..000000000 --- a/docs/plugins/digtype.rst +++ /dev/null @@ -1,19 +0,0 @@ -digtype -======= -For every tile on the map of the same vein type as the selected tile, -this command designates it to have the same designation as the -selected tile. If the selected tile has no designation, they will be -dig designated. -If an argument is given, the designation of the selected tile is -ignored, and all appropriate tiles are set to the specified -designation. - -Options: - -:dig: -:channel: -:ramp: -:updown: up/down stairs -:up: up stairs -:down: down stairs -:clear: clear designation diff --git a/plugins/dfstream.cpp b/plugins/dfstream.cpp deleted file mode 100644 index eb98c181b..000000000 --- a/plugins/dfstream.cpp +++ /dev/null @@ -1,316 +0,0 @@ -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" - -#include "DataDefs.h" -#include "df/graphic.h" -#include "df/enabler.h" -#include "df/renderer.h" - -#include -#include -#include "PassiveSocket.h" -#include "tinythread.h" - -using namespace DFHack; -using namespace df::enums; - -using std::string; -using std::vector; - -DFHACK_PLUGIN("dfstream"); -REQUIRE_GLOBAL(gps); -REQUIRE_GLOBAL(enabler); - -// Owns the thread that accepts TCP connections and forwards messages to clients; -// has a mutex -class client_pool { - typedef tthread::mutex mutex; - - mutex clients_lock; - std::vector clients; - - // TODO - delete this at some point - tthread::thread * accepter; - - static void accept_clients(void * client_pool_pointer) { - client_pool * p = reinterpret_cast(client_pool_pointer); - CPassiveSocket socket; - socket.Initialize(); - if (socket.Listen((const uint8_t *)"0.0.0.0", 8008)) { - std::cout << "Listening on a socket" << std::endl; - } else { - std::cout << "Not listening: " << socket.GetSocketError() << std::endl; - std::cout << socket.DescribeError() << std::endl; - } - while (true) { - CActiveSocket * client = socket.Accept(); - if (client != 0) { - lock l(*p); - p->clients.push_back(client); - } - } - } - -public: - class lock { - tthread::lock_guard l; - public: - lock(client_pool & p) - : l(p.clients_lock) - { - } - }; - friend class client_pool::lock; - - client_pool() { - accepter = new tthread::thread(accept_clients, this); - } - - // MUST have lock - bool has_clients() { - return !clients.empty(); - } - - // MUST have lock - void add_client(CActiveSocket * sock) { - clients.push_back(sock); - } - - // MUST have lock - void broadcast(const std::string & message) { - unsigned int sz = htonl(message.size()); - for (size_t i = 0; i < clients.size(); ++i) { - clients[i]->Send(reinterpret_cast(&sz), sizeof(sz)); - clients[i]->Send((const uint8_t *) message.c_str(), message.size()); - } - } -}; - -// A decorator (in the design pattern sense) of the DF renderer class. -// Sends the screen contents to a client_pool. -class renderer_decorator : public df::renderer { - // the renderer we're decorating - df::renderer * inner; - - // how many frames have passed since we last sent a frame - int framesNotPrinted; - - // set to false in the destructor - bool * alive; - - // clients to which we send the frame - client_pool clients; - - // The following three methods facilitate copying of state to the inner object - void set_to_null() { - screen = NULL; - screentexpos = NULL; - screentexpos_addcolor = NULL; - screentexpos_grayscale = NULL; - screentexpos_cf = NULL; - screentexpos_cbr = NULL; - screen_old = NULL; - screentexpos_old = NULL; - screentexpos_addcolor_old = NULL; - screentexpos_grayscale_old = NULL; - screentexpos_cf_old = NULL; - screentexpos_cbr_old = NULL; - } - - void copy_from_inner() { - screen = inner->screen; - screentexpos = inner->screentexpos; - screentexpos_addcolor = inner->screentexpos_addcolor; - screentexpos_grayscale = inner->screentexpos_grayscale; - screentexpos_cf = inner->screentexpos_cf; - screentexpos_cbr = inner->screentexpos_cbr; - screen_old = inner->screen_old; - screentexpos_old = inner->screentexpos_old; - screentexpos_addcolor_old = inner->screentexpos_addcolor_old; - screentexpos_grayscale_old = inner->screentexpos_grayscale_old; - screentexpos_cf_old = inner->screentexpos_cf_old; - screentexpos_cbr_old = inner->screentexpos_cbr_old; - } - - void copy_to_inner() { - inner->screen = screen; - inner->screentexpos = screentexpos; - inner->screentexpos_addcolor = screentexpos_addcolor; - inner->screentexpos_grayscale = screentexpos_grayscale; - inner->screentexpos_cf = screentexpos_cf; - inner->screentexpos_cbr = screentexpos_cbr; - inner->screen_old = screen_old; - inner->screentexpos_old = screentexpos_old; - inner->screentexpos_addcolor_old = screentexpos_addcolor_old; - inner->screentexpos_grayscale_old = screentexpos_grayscale_old; - inner->screentexpos_cf_old = screentexpos_cf_old; - inner->screentexpos_cbr_old = screentexpos_cbr_old; - } - -public: - renderer_decorator(df::renderer * inner, bool * alive) - : inner(inner) - , framesNotPrinted(0) - , alive(alive) - { - copy_from_inner(); - } - virtual void update_tile(int x, int y) { - copy_to_inner(); - inner->update_tile(x, y); - } - virtual void update_all() { - copy_to_inner(); - inner->update_all(); - } - virtual void render() { - copy_to_inner(); - inner->render(); - - ++framesNotPrinted; - int gfps = enabler->calculated_gfps; - if (gfps == 0) gfps = 1; - // send a frame roughly every 128 mibiseconds (1 second = 1024 mibiseconds) - if ((framesNotPrinted * 1024) / gfps <= 128) return; - - client_pool::lock lock(clients); - if (!clients.has_clients()) return; - framesNotPrinted = 0; - std::stringstream frame; - frame << gps->dimx << ' ' << gps->dimy << " 0 0 " << gps->dimx << ' ' << gps->dimy << '\n'; - unsigned char * sc_ = gps->screen; - for (int y = 0; y < gps->dimy; ++y) { - unsigned char * sc = sc_; - for (int x = 0; x < gps->dimx; ++x) { - unsigned char ch = sc[0]; - unsigned char bold = (sc[3] != 0) * 8; - unsigned char translate[] = - { 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15 }; - unsigned char fg = translate[(sc[1] + bold) % 16]; - unsigned char bg = translate[sc[2] % 16]*16; - frame.put(ch); - frame.put(fg+bg); - sc += 4*gps->dimy; - } - sc_ += 4; - } - clients.broadcast(frame.str()); - } - virtual void set_fullscreen() { inner->set_fullscreen(); } - virtual void zoom(df::zoom_commands cmd) { - copy_to_inner(); - inner->zoom(cmd); - } - virtual void resize(int w, int h) { - copy_to_inner(); - inner->resize(w, h); - copy_from_inner(); - } - virtual void grid_resize(int w, int h) { - copy_to_inner(); - inner->grid_resize(w, h); - copy_from_inner(); - } - virtual ~renderer_decorator() { - *alive = false; - if (inner) { - copy_to_inner(); - delete inner; - inner = 0; - } - set_to_null(); - } - virtual bool get_mouse_coords(int *x, int *y) { return inner->get_mouse_coords(x, y); } - virtual bool uses_opengl() { return inner->uses_opengl(); } - - static renderer_decorator * hook(df::renderer *& ptr, bool * alive) { - renderer_decorator * r = new renderer_decorator(ptr, alive); - ptr = r; - return r; - } - - static void unhook(df::renderer *& ptr, renderer_decorator * dec, color_ostream & out) { - dec->copy_to_inner(); - ptr = dec->inner; - dec->inner = 0; - delete dec; - } -}; - -inline df::renderer *& active_renderer() { - return enabler->renderer; -} - -// This class is a smart pointer around a renderer_decorator. -// It should only be assigned r_d pointers that use the alive-pointer of this -// instance. -// If the r_d has been deleted by an external force, this smart pointer doesn't -// redelete it. -class auto_renderer_decorator { - renderer_decorator * p; -public: - // pass this member to the ctor of renderer_decorator - bool alive; - - auto_renderer_decorator() - : p(0) - { - } - - ~auto_renderer_decorator() { - reset(); - } - - void reset() { - if (*this) { - delete p; - p = 0; - } - } - - operator bool() { - return (p != 0) && alive; - } - - auto_renderer_decorator & operator=(renderer_decorator *p) { - reset(); - this->p = p; - return *this; - } - - renderer_decorator * get() { - return p; - } - - renderer_decorator * operator->() { - return get(); - } -}; - -auto_renderer_decorator decorator; - -DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) -{ - if (!df::renderer::_identity.can_instantiate()) - { - out.printerr("Cannot allocate a renderer\n"); - return CR_OK; - } - if (!decorator) { - decorator = renderer_decorator::hook(active_renderer(), &decorator.alive); - } - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - if (decorator && active_renderer() == decorator.get()) - { - renderer_decorator::unhook(active_renderer(), decorator.get(), out); - } - decorator.reset(); - return CR_OK; -} -// vim:set sw=4 sts=4 et: From 0858b95c408a07318b17e0e8ad1b84c3cfc9f104 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 17:45:48 -0700 Subject: [PATCH 288/854] print help from helpdb on CR_WRONG_USAGE --- library/Core.cpp | 6 +++++- library/PluginManager.cpp | 2 -- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 9b4b065a1..12675054c 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1197,7 +1197,11 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v else { res = plug_mgr->InvokeCommand(con, first, parts); - if(res == CR_NOT_IMPLEMENTED) + if (res == CR_WRONG_USAGE) + { + help_helper(con, first); + } + else if (res == CR_NOT_IMPLEMENTED) { string completed; string filename = findScript(first + ".lua"); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index a1b1bd293..223abfeac 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -503,8 +503,6 @@ command_result Plugin::invoke(color_ostream &out, const std::string & command, s { cr = cmd.function(out, parameters); } - if (cr == CR_WRONG_USAGE && !cmd.usage.empty()) - out << "Usage:\n" << cmd.usage << flush; break; } } From 0f3811b93393bb754c6c558140f7d2c9e40e7c64 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 17:46:11 -0700 Subject: [PATCH 289/854] update debugfilter docs --- docs/plugins/debug.rst | 149 ++++++++++++++++++----------------------- plugins/debug.cpp | 121 +++++---------------------------- 2 files changed, 81 insertions(+), 189 deletions(-) diff --git a/docs/plugins/debug.rst b/docs/plugins/debug.rst index 2bef7d28a..331e675ab 100644 --- a/docs/plugins/debug.rst +++ b/docs/plugins/debug.rst @@ -1,89 +1,72 @@ debug ===== -Manager for DFHack runtime debug prints. Debug prints are grouped by plugin name, -category name and print level. Levels are ``trace``, ``debug``, ``info``, -``warning`` and ``error``. + +Tags: +:dfhack-keybind:`debugfilter` + +:index:`Configure verbosity of DFHack debug output. +` Debug output is +grouped by plugin name, category name, and verbosity level. + +The verbosity levels are: + +- ``Trace`` + Possibly very noisy messages which can be printed many times per second. +- ``Debug`` + Messages that happen often but they should happen only a couple of times per + second. +- ``Info`` + Important state changes that happen rarely during normal execution. +- ``Warning`` + Enabled by default. Shows warnings about unexpected events which code + managed to handle correctly. +- ``Error`` + Enabled by default. Shows errors which code can't handle without user + intervention. The runtime message printing is controlled using filters. Filters set the -visible messages of all matching categories. Matching uses regular expression syntax, -which allows listing multiple alternative matches or partial name matches. -This syntax is a C++ version of the ECMA-262 grammar (Javascript regular expressions). -Details of differences can be found at +visible messages of all matching categories. Matching uses regular expression +syntax, which allows listing multiple alternative matches or partial name +matches. This syntax is a C++ version of the ECMA-262 grammar (Javascript +regular expressions). Details of differences can be found at https://en.cppreference.com/w/cpp/regex/ecmascript -Persistent filters are stored in ``dfhack-config/runtime-debug.json``. -Oldest filters are applied first. That means a newer filter can override the -older printing level selection. - -Usage: ``debugfilter [subcommand] [parameters...]`` - -The following subcommands are supported: - -help ----- -Give overall help or a detailed help for a subcommand. - -Usage: ``debugfilter help [subcommand]`` - -category --------- -List available debug plugin and category names. - -Usage: ``debugfilter category [plugin regex] [category regex]`` - -The list can be filtered using optional regex parameters. If filters aren't -given then the it uses ``"."`` regex which matches any character. The regex -parameters are good way to test regex before passing them to ``set``. - -filter ------- -List active and passive debug print level changes. - -Usage: ``debugfilter filter [id]`` - -Optional ``id`` parameter is the id listed as first column in the filter list. -If id is given then the command shows information for the given filter only in -multi line format that is better format if filter has long regex. - -set ---- -Creates a new debug filter to set category printing levels. - -Usage: ``debugfilter set [level] [plugin regex] [category regex]`` - -Adds a filter that will be deleted when DF process exists or plugin is unloaded. - -Usage: ``debugfilter set persistent [level] [plugin regex] [category regex]`` - -Stores the filter in the configuration file to until ``unset`` is used to remove -it. - -Level is the minimum debug printing level to show in log. - -* ``trace``: Possibly very noisy messages which can be printed many times per second - -* ``debug``: Messages that happen often but they should happen only a couple of times per second - -* ``info``: Important state changes that happen rarely during normal execution - -* ``warning``: Enabled by default. Shows warnings about unexpected events which code managed to handle correctly. - -* ``error``: Enabled by default. Shows errors which code can't handle without user intervention. - -unset ------ -Delete a space separated list of filters - -Usage: ``debugfilter unset [id...]`` - -disable -------- -Disable a space separated list of filters but keep it in the filter list - -Usage: ``debugfilter disable [id...]`` - -enable ------- -Enable a space sperate list of filters - -Usage: ``debugfilter enable [id...]`` +Persistent filters are stored in ``dfhack-config/runtime-debug.json``. Oldest +filters are applied first. That means a newer filter can override the older +printing level selection. + +Usage: + +- ``debugfilter category [] []`` + List available debug plugin and category names. If filters aren't givenm + then all plugins/categories are matched. This command is a good way to test + regex parameters before you pass them to ``set``. +- ``debugfilter filter []`` + List active and passive debug print level changes. The optional ``id`` + parameter is the id listed as first column in the filter list. If ``id`` is + given, then the command shows extended information for the given filter + only. +- ``debugfilter set [] [] []`` + Create a new debug filter to set category verbosity levels. This filter + will not be saved when the DF process exists or the plugin is unloaded. +- ``debugfilter set persistent [] [] []`` + Store the filter in the configuration file to until ``unset`` is used to + remove it. +- ``debugfilter unset [ ...]`` + Delete a space separated list of filters. +- ``debugfilter disable [ ...]`` + Disable a space separated list of filters but keep it in the filter list. +- ``debugfilter enable [ ...]`` + Enable a space sperate list of filters. +- ``debugfilter header [enable] | [disable] [ ...]`` + Control which header metadata is shown along with each log message. Run it + without parameters to see the list of configurable elements. Include an + ``enable`` or ``disable`` keyword to change whether specific elements are + shown. + +Examples: + +- ``debugfilter set Warning core script`` + Hide script execution log messages (e.g. "Loading script: + dfhack-config/dfhack.init"), which are normally output at Info verbosity + in the "core" plugin with the "script" category. diff --git a/plugins/debug.cpp b/plugins/debug.cpp index 4fbc48759..f4a22b8d1 100644 --- a/plugins/debug.cpp +++ b/plugins/debug.cpp @@ -104,85 +104,13 @@ JsonArchive& operator>>(JsonArchive& ar, const serialization::nvp& target) static constexpr auto defaultRegex = std::regex::optimize | std::regex::nosubs | std::regex::collate; -static const char* const commandHelp = - " Manage runtime debug print filters.\n" - "\n" - " debugfilter category [ []]\n" - " List categories matching regular expressions.\n" - " debugfilter filter []\n" - " List active filters or show detailed information for a filter.\n" - " debugfilter set [persistent] [ []]\n" - " Set a filter level to categories matching regular expressions.\n" - " debugfilter unset [ ...]\n" - " Unset filters matching space separated list of ids from 'filter'.\n" - " debugfilter disable [ ...]\n" - " Disable filters matching space separated list of ids from 'filter'.\n" - " debugfilter enable [ ...]\n" - " Enable filters matching space separated list of ids from 'filter'.\n" - " debugfilter header [enable] | [disable] [ ...]\n" - " Control which header metadata is shown along with each log message.\n" - " debugfilter help []\n" - " Show detailed help for a command or this help.\n"; -static const char* const commandCategory = - " category [ []]\n" - " List categories with optional filters. Parameters are passed to\n" - " std::regex to limit which once are shown. The first regular\n" - " expression is used to match the category and the second is used\n" - " to match the plugin name.\n"; -static const char* const commandSet = - " set [persistent] [ []]\n" - " Set filtering level for matching categories. 'level' must be one of\n" - " trace, debug, info, warning and error. The 'level' parameter sets\n" - " the lowest message level that will be shown. The command doesn't\n" - " allow filters to disable any error messages.\n" - " Default filter life time is until Dwarf Fortress process exists or\n" - " plugin is unloaded. Passing 'persistent' as second parameter tells\n" - " the plugin to store the filter to dfhack-config. Stored filters\n" - " will be active until always when the plugin is loaded. 'unset'\n" - " command can be used to remove persistent filters.\n" - " Filters are applied FIFO order. The latest filter will override any\n" - " older filter that also matches.\n"; -static const char* const commandFilters = - " filter []\n" - " Show the list of active filters. The first column is 'id' which can\n" - " be used to deactivate filters using 'unset' command.\n" - " Filters are printed in same order as applied - the oldest first.\n"; -static const char* const commandUnset = - " unset [ ...]\n" - " 'unset' takes space separated list of filter ids from 'filter'.\n" - " It will reset any matching category back to the default 'warning'\n" - " level or any other still active matching filter level.\n"; -static const char* const commandDisable = - " disable [ ...]\n" - " 'disable' takes space separated list of filter ids from 'filter'.\n" - " It will reset any matching category back to the default 'warning'\n" - " level or any other still active matching filter level.\n" - " 'disable' will print red filters that were already disabled.\n"; -static const char* const commandEnable = - " enable [ ...]\n" - " 'enable' takes space separated list of filter ids from 'filter'.\n" - " It will reset any matching category back to the default 'warning'\n" - " level or any other still active matching filter level.\n" - " 'enable' will print red filters that were already enabled.\n"; -static const char* const commandHeader = - " header [enable] | [disable] [ ...]\n" - " 'header' allows you to customize what metadata is displayed with\n" - " each log message. Run it without parameters to see the list of\n" - " configurable elements. Include an 'enable' or 'disable' keyword to\n" - " change specific elements.\n"; -static const char* const commandHelpDetails = - " help []\n" - " Show help for any of subcommands. Without any parameters it shows\n" - " short help for all subcommands.\n"; - //! Helper type to hold static dispatch table for subcommands struct CommandDispatch { //! Store handler function pointer and help message for commands struct Command { using handler_t = command_result(*)(color_ostream&,std::vector&); - Command(handler_t handler, const char* help) : - handler_(handler), - help_(help) + Command(handler_t handler) : + handler_(handler) {} command_result operator()(color_ostream& out, @@ -193,12 +121,8 @@ struct CommandDispatch { handler_t handler() const noexcept { return handler_; } - - const char* help() const noexcept - { return help_; } private: handler_t handler_; - const char* help_; }; using dispatch_t = const std::map; //! Name to handler function and help message mapping @@ -1125,28 +1049,14 @@ static command_result configureHeader(color_ostream& out, using DFHack::debugPlugin::CommandDispatch; -static command_result printHelp(color_ostream& out, - std::vector& parameters) -{ - const char* help = commandHelp; - auto iter = CommandDispatch::dispatch.end(); - if (1u < parameters.size()) - iter = CommandDispatch::dispatch.find(parameters[1]); - if (iter != CommandDispatch::dispatch.end()) - help = iter->second.help(); - out << help << std::flush; - return CR_OK; -} - CommandDispatch::dispatch_t CommandDispatch::dispatch { - {"category", {listCategories,commandCategory}}, - {"filter", {listFilters,commandFilters}}, - {"set", {setFilter,commandSet}}, - {"unset", {unsetFilter,commandUnset}}, - {"enable", {enableFilter,commandEnable}}, - {"disable", {disableFilter,commandDisable}}, - {"header", {configureHeader,commandHeader}}, - {"help", {printHelp,commandHelpDetails}}, + {"category", {listCategories}}, + {"filter", {listFilters}}, + {"set", {setFilter}}, + {"unset", {unsetFilter}}, + {"enable", {enableFilter}}, + {"disable", {disableFilter}}, + {"header", {configureHeader}}, }; //! Dispatch command handling to the subcommand or help @@ -1156,13 +1066,14 @@ static command_result commandDebugFilter(color_ostream& out, DEBUG(command,out).print("debugfilter %s, parameter count %zu\n", parameters.size() > 0 ? parameters[0].c_str() : "", parameters.size()); - auto handler = printHelp; auto iter = CommandDispatch::dispatch.end(); if (0u < parameters.size()) iter = CommandDispatch::dispatch.find(parameters[0]); - if (iter != CommandDispatch::dispatch.end()) - handler = iter->second.handler(); - return (handler)(out, parameters); + if (iter != CommandDispatch::dispatch.end()) { + iter->second.handler()(out, parameters); + return CR_OK; + } + return CR_WRONG_USAGE; } } } /* namespace debug */ @@ -1173,9 +1084,7 @@ DFhackCExport DFHack::command_result plugin_init(DFHack::color_ostream& out, commands.emplace_back( "debugfilter", "Manage runtime debug print filters", - DFHack::debugPlugin::commandDebugFilter, - false, - DFHack::debugPlugin::commandHelp); + DFHack::debugPlugin::commandDebugFilter); auto& filMan = DFHack::debugPlugin::FilterManager::getInstance(); DFHack::command_result rv = DFHack::CR_OK; if ((rv = filMan.loadConfig(out)) != DFHack::CR_OK) From 675f2edee2ef414089c8076fec8041c3124fcd3d Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 17:46:22 -0700 Subject: [PATCH 290/854] update deramp docs --- docs/plugins/deramp.rst | 15 +++++++++++---- plugins/deramp.cpp | 12 +++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/plugins/deramp.rst b/docs/plugins/deramp.rst index a6b944381..7489e506e 100644 --- a/docs/plugins/deramp.rst +++ b/docs/plugins/deramp.rst @@ -1,6 +1,13 @@ deramp ====== -Removes all ramps designated for removal from the map. This is useful for -replicating the old channel digging designation. It also removes any and -all 'down ramps' that can remain after a cave-in (you don't have to designate -anything for that to happen). + +Tags: +:dfhack-keybind:`deramp` + +:index:`Removes all ramps designated for removal from the map. +` It also +removes any "floating" down ramps that can remain after a cave-in. + +Usage:: + + deramp diff --git a/plugins/deramp.cpp b/plugins/deramp.cpp index 62679cae0..1c397242b 100644 --- a/plugins/deramp.cpp +++ b/plugins/deramp.cpp @@ -109,13 +109,11 @@ command_result df_deramp (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand( - "deramp", "Replace all ramps marked for removal with floors.", - df_deramp, false, - " If there are any ramps designated for removal, they will be instantly\n" - " removed. Any ramps that don't have their counterpart will also be removed\n" - " (fixes bugs with caveins)\n" - )); + commands.push_back( + PluginCommand( + "deramp", + "Removes all ramps designated for removal from the map.", + df_deramp)); return CR_OK; } From 4cb57d25f1fe89012c0d54ad7aa0dee24cde8aff Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 17:46:33 -0700 Subject: [PATCH 291/854] update dig-now docs --- docs/plugins/dig-now.rst | 42 +++++++++++++++++++++------------------- plugins/dig-now.cpp | 23 ++++++---------------- plugins/lua/dig-now.lua | 31 ----------------------------- 3 files changed, 28 insertions(+), 68 deletions(-) diff --git a/docs/plugins/dig-now.rst b/docs/plugins/dig-now.rst index b6f4d64f7..4628f10f5 100644 --- a/docs/plugins/dig-now.rst +++ b/docs/plugins/dig-now.rst @@ -1,10 +1,15 @@ dig-now ======= -Instantly completes non-marker dig designations, modifying tile shapes and -creating boulders, ores, and gems as if a miner were doing the mining or -engraving. By default, the entire map is processed and boulder generation -follows standard game rules, but the behavior is configurable. +Tags: +:dfhack-keybind:`dig-now` + +:index:`Instantly complete dig designations. +` This tool will magically +complete non-marker dig designations, modifying tile shapes and creating +boulders, ores, and gems as if a miner were doing the mining or engraving. By +default, the entire map is processed and boulder generation follows standard +game rules, but the behavior is configurable. Note that no units will get mining or engraving experience for the dug/engraved tiles. @@ -25,37 +30,34 @@ that coordinate is processed. Any ```` parameters can either be an ``,,`` triple (e.g. ``35,12,150``) or the string ``here``, which means the position of the active -game cursor should be used. +game cursor should be used. You can use the `position` command to get the +current cursor position if you need it. Examples: -``dig-now`` +- ``dig-now`` Dig designated tiles according to standard game rules. - -``dig-now --clean`` - Dig designated tiles, but don't generate any boulders, ores, or gems. - -``dig-now --dump here`` - Dig tiles and dump all generated boulders, ores, and gems at the tile under - the game cursor. +- ``dig-now --clean`` + Dig all designated tiles, but don't generate any boulders, ores, or gems. +- ``dig-now --dump here`` + Dig tiles and teleport all generated boulders, ores, and gems to the tile + under the game cursor. Options: -:``-c``, ``--clean``: +- ``-c``, ``--clean`` Don't generate any boulders, ores, or gems. Equivalent to ``--percentages 0,0,0,0``. -:``-d``, ``--dump ``: +- ``-d``, ``--dump `` Dump any generated items at the specified coordinates. If the tile at those coordinates is open space or is a wall, items will be generated on the closest walkable tile below. -:``-e``, ``--everywhere``: +- ``-e``, ``--everywhere`` Generate a boulder, ore, or gem for every tile that can produce one. Equivalent to ``--percentages 100,100,100,100``. -:``-h``, ``--help``: - Show quick usage help text. -:``-p``, ``--percentages ,,,``: +- ``-p``, ``--percentages ,,,`` Set item generation percentages for each of the tile categories. The ``vein`` category includes both the large oval clusters and the long stringy mineral veins. Default is ``25,33,100,100``. -:``-z``, ``--cur-zlevel``: +- ``-z``, ``--cur-zlevel`` Restricts the bounds to the currently visible z-level. diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 78b2b1522..bbcabde65 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -838,17 +838,6 @@ static bool get_options(color_ostream &out, return true; } -static void print_help(color_ostream &out) { - auto L = Lua::Core::State; - Lua::StackUnwinder top(L); - - if (!lua_checkstack(L, 1) || - !Lua::PushModulePublic(out, L, "plugins.dig-now", "print_help") || - !Lua::SafeCall(out, L, 0, 0)) { - out.printerr("Failed to load dig-now Lua code\n"); - } -} - bool dig_now_impl(color_ostream &out, const dig_now_options &options) { if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); @@ -880,18 +869,18 @@ command_result dig_now(color_ostream &out, std::vector ¶ms) { dig_now_options options; if (!get_options(out, options, params) || options.help) - { - print_help(out); - return options.help ? CR_OK : CR_FAILURE; - } + return CR_WRONG_USAGE; return dig_now_impl(out, options) ? CR_OK : CR_FAILURE; } DFhackCExport command_result plugin_init(color_ostream &, std::vector &commands) { - commands.push_back(PluginCommand( - "dig-now", "Instantly complete dig designations", dig_now, false)); + commands.push_back( + PluginCommand( + "dig-now", + "Instantly complete dig designations.", + dig_now)); return CR_OK; } diff --git a/plugins/lua/dig-now.lua b/plugins/lua/dig-now.lua index 2d7ae40d7..b3ffeb0bc 100644 --- a/plugins/lua/dig-now.lua +++ b/plugins/lua/dig-now.lua @@ -4,37 +4,6 @@ local argparse = require('argparse') local guidm = require('gui.dwarfmode') local utils = require('utils') -local short_help_text = [=[ - -dig-now -======= - -Instantly completes dig designations, modifying map tiles and creating boulders, -ores, and gems as if a miner were doing the mining or engraving. By default, all -dig designations on the map are completed and boulder generation follows -standard game rules, but the behavior is configurable. - -Usage: - - dig-now [ []] [] - -Examples: - -dig-now - Dig all designated tiles according to standard game rules. - -dig-now --clean - Dig designated tiles, but don't generate any boulders, ores, or gems. - -dig-now --dump here - Dig tiles and dump all generated boulders, ores, and gems at the tile under - the game cursor. - -See the online DFHack documentation for details on all options. -]=] - -function print_help() print(short_help_text) end - local function parse_coords(opts, configname, arg) local cursor = argparse.coords(arg, configname) utils.assign(opts[configname], cursor) From a7346dd05b3bb653b9707b574f252b995de495b9 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 17:51:48 -0700 Subject: [PATCH 292/854] add missing labels --- docs/plugins/cleaners.rst | 3 + docs/plugins/cromulate.rst | 2 +- docs/plugins/dig.rst | 86 ++++++++++++++++++++++++++ docs/plugins/filltraffic.rst | 13 ++++ docs/plugins/zone.rst | 113 +++++++++++++++++++++++++++++++++++ 5 files changed, 216 insertions(+), 1 deletion(-) diff --git a/docs/plugins/cleaners.rst b/docs/plugins/cleaners.rst index b290a73a4..9578bb22a 100644 --- a/docs/plugins/cleaners.rst +++ b/docs/plugins/cleaners.rst @@ -1,3 +1,6 @@ +.. _clean: +.. _spotclean: + cleaners ======== diff --git a/docs/plugins/cromulate.rst b/docs/plugins/cromulate.rst index b025fe877..6e659989c 100644 --- a/docs/plugins/cromulate.rst +++ b/docs/plugins/cromulate.rst @@ -1,7 +1,7 @@ cromulate ========= -Tags: `tag/productivity`, `tag/unit`, `tag/adventure` +Tags: `tag/productivity`, `tag/units`, `tag/adventure` :dfhack-keybind:`cromulate` :index:`Collects all widgets into a frobozz electric cromufiler. diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst index 2b1e96a18..6026bd88a 100644 --- a/docs/plugins/dig.rst +++ b/docs/plugins/dig.rst @@ -1,4 +1,5 @@ .. _digv: +.. _digtype: dig === @@ -20,3 +21,88 @@ Basic commands: specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, where ``#`` is a number from 1 to 7. If a priority is not specified, the priority selected in-game is used as the default. +digcircle +========= +A command for easy designation of filled and hollow circles. +It has several types of options. + +Shape: + +:hollow: Set the circle to hollow (default) +:filled: Set the circle to filled +:#: Diameter in tiles (default = 0, does nothing) + +Action: + +:set: Set designation (default) +:unset: Unset current designation +:invert: Invert designations already present + +Designation types: + +:dig: Normal digging designation (default) +:ramp: Ramp digging +:ustair: Staircase up +:dstair: Staircase down +:xstair: Staircase up/down +:chan: Dig channel + +After you have set the options, the command called with no options +repeats with the last selected parameters. + +Examples: + +``digcircle filled 3`` + Dig a filled circle with diameter = 3. +``digcircle`` + Do it again. +digtype +======= +For every tile on the map of the same vein type as the selected tile, +this command designates it to have the same designation as the +selected tile. If the selected tile has no designation, they will be +dig designated. +If an argument is given, the designation of the selected tile is +ignored, and all appropriate tiles are set to the specified +designation. + +Options: + +:dig: +:channel: +:ramp: +:updown: up/down stairs +:up: up stairs +:down: down stairs +:clear: clear designation +digexp +====== +This command is for :wiki:`exploratory mining `. + +There are two variables that can be set: pattern and filter. + +Patterns: + +:diag5: diagonals separated by 5 tiles +:diag5r: diag5 rotated 90 degrees +:ladder: A 'ladder' pattern +:ladderr: ladder rotated 90 degrees +:clear: Just remove all dig designations +:cross: A cross, exactly in the middle of the map. + +Filters: + +:all: designate whole z-level +:hidden: designate only hidden tiles of z-level (default) +:designated: Take current designation and apply pattern to it. + +After you have a pattern set, you can use ``expdig`` to apply it again. + +Examples: + +``expdig diag5 hidden`` + Designate the diagonal 5 patter over all hidden tiles +``expdig`` + Apply last used pattern and filter +``expdig ladder designated`` + Take current designations and replace them with the ladder pattern diff --git a/docs/plugins/filltraffic.rst b/docs/plugins/filltraffic.rst index 6a9c57c9f..1f8768ec3 100644 --- a/docs/plugins/filltraffic.rst +++ b/docs/plugins/filltraffic.rst @@ -1,3 +1,5 @@ +.. _alltraffic: + filltraffic =========== Set traffic designations using flood-fill starting at the cursor. @@ -15,3 +17,14 @@ Example: ``filltraffic H`` When used in a room with doors, it will set traffic to HIGH in just that room. +alltraffic +========== +Set traffic designations for every single tile of the map - useful for resetting +traffic designations. See also `filltraffic`, `restrictice`, and `restrictliquids`. + +Options: + +:H: High Traffic +:N: Normal Traffic +:L: Low Traffic +:R: Restricted Traffic diff --git a/docs/plugins/zone.rst b/docs/plugins/zone.rst index a3112c147..08d561bbc 100644 --- a/docs/plugins/zone.rst +++ b/docs/plugins/zone.rst @@ -1,3 +1,6 @@ +.. _autobutcher: +.. _autonestbox: + zone ==== Helps a bit with managing activity zones (pens, pastures and pits) and cages. @@ -128,3 +131,113 @@ Examples ``zone tocages count 50 own tame male not grazer`` Stuff up to 50 owned tame male animals who are not grazers into cages built on the current default zone. +autobutcher +=========== +Assigns lifestock for slaughter once it reaches a specific count. Requires that +you add the target race(s) to a watch list. Only tame units will be processed. + +Units will be ignored if they are: + +* Nicknamed (for custom protection; you can use the `rename` ``unit`` tool + individually, or `zone` ``nick`` for groups) +* Caged, if and only if the cage is defined as a room (to protect zoos) +* Trained for war or hunting + +Creatures who will not reproduce (because they're not interested in the +opposite sex or have been gelded) will be butchered before those who will. +Older adults and younger children will be butchered first if the population +is above the target (default 1 male, 5 female kids and adults). Note that +you may need to set a target above 1 to have a reliable breeding population +due to asexuality etc. See `fix-ster` if this is a problem. + +Options: + +:example: Print some usage examples. +:start: Start running every X frames (df simulation ticks). + Default: X=6000, which would be every 60 seconds at 100fps. +:stop: Stop running automatically. +:sleep : Changes the timer to sleep X frames between runs. +:watch R: Start watching a race. R can be a valid race RAW id (ALPACA, + BIRD_TURKEY, etc) or a list of ids seperated by spaces or + the keyword 'all' which affects all races on your current + watchlist. +:unwatch R: Stop watching race(s). The current target settings will be + remembered. R can be a list of ids or the keyword 'all'. +:forget R: Stop watching race(s) and forget it's/their target settings. + R can be a list of ids or the keyword 'all'. +:autowatch: Automatically adds all new races (animals you buy from merchants, + tame yourself or get from migrants) to the watch list using + default target count. +:noautowatch: Stop auto-adding new races to the watchlist. +:list: Print the current status and watchlist. +:list_export: Print the commands needed to set up status and watchlist, + which can be used to import them to another save (see notes). +:target : + Set target count for specified race(s). The first four arguments + are the number of female and male kids, and female and male adults. + R can be a list of spceies ids, or the keyword ``all`` or ``new``. + ``R = 'all'``: change target count for all races on watchlist + and set the new default for the future. ``R = 'new'``: don't touch + current settings on the watchlist, only set the new default + for future entries. +:list_export: Print the commands required to rebuild your current settings. + +.. note:: + + Settings and watchlist are stored in the savegame, so that you can have + different settings for each save. If you want to copy your watchlist to + another savegame you must export the commands required to recreate your settings. + + To export, open an external terminal in the DF directory, and run + ``dfhack-run autobutcher list_export > filename.txt``. To import, load your + new save and run ``script filename.txt`` in the DFHack terminal. + + +Examples: + +You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, +1 male) of the race alpaca. Once the kids grow up the oldest adults will get +slaughtered. Excess kids will get slaughtered starting with the youngest +to allow that the older ones grow into adults. Any unnamed cats will +be slaughtered as soon as possible. :: + + autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY + autobutcher target 0 0 0 0 CAT + autobutcher watch ALPACA BIRD_TURKEY CAT + autobutcher start + +Automatically put all new races onto the watchlist and mark unnamed tame units +for slaughter as soon as they arrive in your fort. Settings already made +for specific races will be left untouched. :: + + autobutcher target 0 0 0 0 new + autobutcher autowatch + autobutcher start + +Stop watching the races alpaca and cat, but remember the target count +settings so that you can use 'unwatch' without the need to enter the +values again. Note: 'autobutcher unwatch all' works, but only makes sense +if you want to keep the plugin running with the 'autowatch' feature or manually +add some new races with 'watch'. If you simply want to stop it completely use +'autobutcher stop' instead. :: + + autobutcher unwatch ALPACA CAT +autonestbox +=========== +Assigns unpastured female egg-layers to nestbox zones. Requires that you create +pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox +must be in the top left corner. Only 1 unit will be assigned per pen, regardless +of the size. The age of the units is currently not checked, most birds grow up +quite fast. Egglayers who are also grazers will be ignored, since confining them +to a 1x1 pasture is not a good idea. Only tame and domesticated own units are +processed since pasturing half-trained wild egglayers could destroy your neat +nestbox zones when they revert to wild. When called without options autonestbox +will instantly run once. + +Options: + +:start: Start running every X frames (df simulation ticks). + Default: X=6000, which would be every 60 seconds at 100fps. +:stop: Stop running automatically. +:sleep: Must be followed by number X. Changes the timer to sleep X + frames between runs. From 5dde613a12bbdd9aae1f80979e34b2035748abca Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 19:06:50 -0700 Subject: [PATCH 293/854] fix formatting errors --- docs/plugins/dig.rst | 3 +++ docs/plugins/filltraffic.rst | 1 + docs/plugins/zone.rst | 2 ++ 3 files changed, 6 insertions(+) diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst index 6026bd88a..c4811883b 100644 --- a/docs/plugins/dig.rst +++ b/docs/plugins/dig.rst @@ -21,6 +21,7 @@ Basic commands: specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, where ``#`` is a number from 1 to 7. If a priority is not specified, the priority selected in-game is used as the default. + digcircle ========= A command for easy designation of filled and hollow circles. @@ -56,6 +57,7 @@ Examples: Dig a filled circle with diameter = 3. ``digcircle`` Do it again. + digtype ======= For every tile on the map of the same vein type as the selected tile, @@ -75,6 +77,7 @@ Options: :up: up stairs :down: down stairs :clear: clear designation + digexp ====== This command is for :wiki:`exploratory mining `. diff --git a/docs/plugins/filltraffic.rst b/docs/plugins/filltraffic.rst index 1f8768ec3..93306e9eb 100644 --- a/docs/plugins/filltraffic.rst +++ b/docs/plugins/filltraffic.rst @@ -17,6 +17,7 @@ Example: ``filltraffic H`` When used in a room with doors, it will set traffic to HIGH in just that room. + alltraffic ========== Set traffic designations for every single tile of the map - useful for resetting diff --git a/docs/plugins/zone.rst b/docs/plugins/zone.rst index 08d561bbc..be827b47c 100644 --- a/docs/plugins/zone.rst +++ b/docs/plugins/zone.rst @@ -131,6 +131,7 @@ Examples ``zone tocages count 50 own tame male not grazer`` Stuff up to 50 owned tame male animals who are not grazers into cages built on the current default zone. + autobutcher =========== Assigns lifestock for slaughter once it reaches a specific count. Requires that @@ -222,6 +223,7 @@ add some new races with 'watch'. If you simply want to stop it completely use 'autobutcher stop' instead. :: autobutcher unwatch ALPACA CAT + autonestbox =========== Assigns unpastured female egg-layers to nestbox zones. Requires that you create From cb3e537b383e06dfc00c89e184ead77d58acdc8b Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 20:57:19 -0700 Subject: [PATCH 294/854] fix help message when trying to run a plugin name --- library/Core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index 12675054c..72ddc33aa 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1227,7 +1227,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v con.printerr("that is not loaded - try \"load %s\" or check stderr.log\n", first.c_str()); else if (p->size()) - con.printerr("that implements %zi commands - see \"ls %s\" for details\n", + con.printerr("that implements %zi commands - see \"help %s\" for details\n", p->size(), first.c_str()); else con.printerr("but does not implement any commands\n"); From 3141ecbec262d826e4699d64aa6702952cc821c4 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 21:04:22 -0700 Subject: [PATCH 295/854] remove cromulate. it has outlived its purpose --- docs/plugins/cromulate.rst | 36 ------------------------------------ plugins/CMakeLists.txt | 1 - plugins/cromulate.cpp | 26 -------------------------- 3 files changed, 63 deletions(-) delete mode 100644 docs/plugins/cromulate.rst delete mode 100644 plugins/cromulate.cpp diff --git a/docs/plugins/cromulate.rst b/docs/plugins/cromulate.rst deleted file mode 100644 index 6e659989c..000000000 --- a/docs/plugins/cromulate.rst +++ /dev/null @@ -1,36 +0,0 @@ -cromulate -========= - -Tags: `tag/productivity`, `tag/units`, `tag/adventure` -:dfhack-keybind:`cromulate` - -:index:`Collects all widgets into a frobozz electric cromufiler. -` You might -want to do this if you discover that your widgets have become decromulated. It -is safe to run this command periodically even if you are unsure if that's the -case. - -Usage:: - - cromulate [all|here] [] - -When run without parameters, it lists all your widgets. Add the ``all`` keyword -to collect all widgets into the cromufiler, or the ``here`` keyword to just -collect those under the cursor. - -Options: - -- ``-d``, ``--destroy`` - Destroy the widgets instead of collecting them into the cromufiler. -- ``-q``, ``--quiet`` - Don't display any informational output. Errors will still be printed to the - console. - -Examples: - -- ``cromulate`` - Lists all widgets and their positions -- ``cromlate all`` - Gather all widgets into the cromufiler -- ``cromulate here --destroy`` - Destroys the widgets under the cursor diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index b9d63839e..283f74fad 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -105,7 +105,6 @@ if(BUILD_SUPPORTED) dfhack_plugin(command-prompt command-prompt.cpp) dfhack_plugin(confirm confirm.cpp LINK_LIBRARIES lua) dfhack_plugin(createitem createitem.cpp) - dfhack_plugin(cromulate cromulate.cpp) dfhack_plugin(cursecheck cursecheck.cpp) dfhack_plugin(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) dfhack_plugin(deramp deramp.cpp) diff --git a/plugins/cromulate.cpp b/plugins/cromulate.cpp deleted file mode 100644 index 7ed9ed115..000000000 --- a/plugins/cromulate.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "Core.h" -#include -#include -#include - -using namespace DFHack; -using namespace df::enums; - -DFHACK_PLUGIN("cromulate"); - -command_result cromulate (color_ostream &out, std::vector & parameters); - -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("cromulate", - "in-cpp plugin short desc", //to use one line in the ``[DFHack]# ls`` output - cromulate)); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown (color_ostream &out) { - return CR_OK; -} - -command_result cromulate (color_ostream &out, std::vector ¶meters) { - return CR_OK; -} From 9b7cc6180de2b082035eeadf95e0afc9b95a81a5 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 22 Jul 2022 21:21:38 -0700 Subject: [PATCH 296/854] don't create entries for non-enableable plugins --- library/lua/helpdb.lua | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index c660a37dd..8b35b0d25 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -273,11 +273,13 @@ local function update_db(old_db, entry_name, text_entry, help_source, kwargs) -- already in db (e.g. from a higher-priority script dir); skip return end - entrydb[entry_name] = { - entry_types=kwargs.entry_types, - short_help=kwargs.short_help, - text_entry=text_entry - } + if not kwargs.text_entry_only then + entrydb[entry_name] = { + entry_types=kwargs.entry_types, + short_help=kwargs.short_help, + text_entry=text_entry + } + end if entry_name ~= text_entry then return end @@ -329,13 +331,16 @@ local function scan_plugins(old_db) kwargs) has_commands = true end - if not includes_plugin and (has_commands or - dfhack.internal.isPluginEnableable(plugin)) then + if includes_plugin then goto continue end + local is_enableable = dfhack.internal.isPluginEnableable(plugin) + if has_commands or is_enableable then update_db(old_db, plugin, plugin, has_rendered_help(plugin) and HELP_SOURCES.RENDERED or HELP_SOURCES.STUB, - {entry_types={[ENTRY_TYPES.PLUGIN]=true}}) + {entry_types={[ENTRY_TYPES.PLUGIN]=true}, + text_entry_only=not is_enableable}) end + ::continue:: end end From 62523bdcb1a2da3083b219ef5d320d5f7f828a00 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 23 Jul 2022 15:13:11 -0700 Subject: [PATCH 297/854] update documentation for the documentation --- docs/Documentation.rst | 304 ++++++++++++++++++++++++++++++----------- 1 file changed, 225 insertions(+), 79 deletions(-) diff --git a/docs/Documentation.rst b/docs/Documentation.rst index c035a0366..6be4de37f 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -14,11 +14,15 @@ compiled to HTML, such as automatic tables of contents, cross-linking, special external links (forum, wiki, etc) and more. The documentation is compiled by a Python tool, `Sphinx `_. -The DFHack build process will compile the documentation, but this is disabled -by default due to the additional Python and Sphinx requirements. You typically -only need to build the docs if you're changing them, or perhaps -if you want a local HTML copy; otherwise, you can read an -`online version hosted by ReadTheDocs `_. +The DFHack build process will compile and install the documentation so it can be +displayed in-game by the `help` and `ls` commands (and any other command or GUI that +displays help text), but this is disabled by default due to the additional Python and +Sphinx requirements. If you already have a version of the docs installed (say from a +downloaded release binary), then you only need to build the docs if you're changing them +and want to see the changes reflected in your game. + +You can also build the docs if you just want a local HTML- or text-rendered copy, though +you can always read the `online version `_. (Note that even if you do want a local copy, it is certainly not necessary to compile the documentation in order to read it. Like Markdown, reST documents are @@ -28,6 +32,63 @@ The main thing you lose in plain text format is hyperlinking.) .. contents:: Contents :local: +Concepts and general guidance +============================= + +The source ``.rst`` files are compiled to HTML for viewing in a browser and to text +format for viewing in-game. For in-game help, the help text is read from its installed +location in ``hack/docs`` under the DF directory for in-game display. + +Remember, everything should be documented! If it's not clear *where* a particular thing +should be documented, ask on Discord or in the DFHack thread on Bay12 -- as well as +getting help, you'll be providing valuable feedback that makes it easier for future readers! + +Try to keep lines within 80-100 characters, so it's readable in plain text +in the terminal - Sphinx (our documentation system) will make sure +paragraphs flow. + +Short descriptions +------------------ + +Each command that a user can run, as well as every plugin that can be enabled for some +lasting effect, needs to have a short (~54 character) descriptive string associated with +it. This description text is: + +- used in-game by the `ls` command and DFHack UI screens that list commands +- used in the generated index entries in the HTML docs + +Tags +---- + +To make it easier for players to find related commands, all plugins and commands are marked +with relevant tags. These are used to compile indices and generate cross-links between the +commands, both in the HTML documents and in-game. See the list of available tags +`here ` and think about which categories your new tools belongs in. + +Links +----- + +If it would be helpful to mention another DFHack command, don't just type the +name - add a hyperlink! Specify the link target in backticks, and it will be +replaced with the corresponding title and linked: e.g. ```autolabor``` +=> `autolabor`. Scripts and plugins have link targets that match their names +created for you automatically. + +If you want to link to a heading in your own page, you can specifiy it like this:: + + `Heading text exactly as written`_ + +Note that the DFHack documentation is configured so that single backticks (with +no prefix or suffix) produce links to internal link targets, such as the +``autolabor`` target shown above. This is different from the reStructuredText +default behavior of rendering such text in italics (as a reference to a title). +For alternative link behaviors, see: + +- `The reStructuredText documentation on roles `__ +- `The reStructuredText documentation on external links `__ +- `The Sphinx documentation on roles `__ + - ``:doc:`` is useful for linking to another document outside of DFHack. + .. _docs-standards: Documentation standards @@ -38,106 +99,191 @@ there are a few important standards for completeness and consistent style. Trea this section as a guide rather than iron law, match the surrounding text, and you'll be fine. -Command documentation ---------------------- +Where do I add the help text? +----------------------------- -Each command should have a short (~54 character) help string, which is shown -by the `ls` command. For scripts, this is a comment on the first line -(the comment marker and whitespace is stripped). For plugins it's the second -argument to ``PluginCommand``. Please make this brief but descriptive! +For scripts and plugins that are distributed as part of DFHack, documentation files +should be added to the :source-scripts:`docs` and :source:`docs/plugins` directories, +respectively, in a file named after the script or plugin. For example, a script named +``gui/foobar.lua`` (which provides the ``gui/foobar`` command) should be documented +in a file named ``docs/gui/foobar.rst`` in the scripts repo. Similarly, a plugin named +``foobaz`` should be documented in a file named ``docs/plugins/foobaz.rst`` in the dfhack repo. +For plugins, all commands provided by that plugin should be documented in that same file. -Everything should be documented! If it's not clear *where* a particular -thing should be documented, ask on IRC or in the DFHack thread on Bay12 - -as well as getting help, you'll be providing valuable feedback that -makes it easier for future readers! +Short descriptions (the ~54 character short help) are taken from the first "sentence" of +the help text for scripts and plugins that can be enabled. This means that the help should +begin with a sentence fragment that begins with a capital letter and ends in a full stop +(``.``). Please make this brief but descriptive! -Scripts can use a custom autodoc function, based on the Sphinx ``include`` -directive - anything between the tokens is copied into the appropriate scripts -documentation page. For Ruby, we follow the built-in docstring convention -(``=begin`` and ``=end``). For Lua, the tokens are ``[====[`` and ``]====]`` -- ordinary multi-line strings. It is highly encouraged to reuse this string -as the in-console documentation by (e.g.) printing it when a ``-help`` argument -is given. +Short descriptions for commands provided by plugins are taken from the ``description`` +parameter passed to the ``PluginCommand`` constructor used when the command is registered +in the plugin source file. -The docs **must** have a heading which exactly matches the command, underlined -with ``=====`` to the same length. For example, a lua file would have: - -.. code-block:: lua +Header format +------------- - local helpstr = [====[ +The docs **must** begin with a heading which exactly matches the script or plugin name, underlined +with ``=====`` to the same length. This should be followed by a ``Tags:`` line with +comma-separated links to the tag indices, and then a ``:dfhack-keybind:`commandname``` line for +each command provided by the script or plugin. For scripts, this will just be the script name. +Plugins that do not provide commands (i.e. they can just be enabled for some persistent effect or +they just export functionality via a Lua API) don't need any ``:dfhack-keybind:`` lines at all. +The first line of the text should then be the short description that will be used for the script +or plugin. For example, documentation for the ``build-now`` script might look like: - add-thought - =========== - Adds a thought or emotion to the selected unit. Can be used by other scripts, - or the gui invoked by running ``add-thought gui`` with a unit selected. +.. code-block:: rst - ]====] + build-now + ========= + Tags: `tag/fort`, `tag/buildings` + :dfhack-keybind:`build-now` + Instantly completes unsuspended building jobs. By default, all constructions + and buildings on the map are completed, but the area of effect is configurable. -.. highlight:: rst +Usage help +---------- -Where the heading for a section is also the name of a command, the spelling -and case should exactly match the command to enter in the DFHack command line. +The first section after the header and introductory text should be the usage block. You can +choose between two formats, based on whatever is cleaner or clearer for your syntax. The first +option is to show usage formats together, with an explanation following the block:: -Try to keep lines within 80-100 characters, so it's readable in plain text -in the terminal - Sphinx (our documentation system) will make sure -paragraphs flow. + Usage:: -Command usage -------------- + build-now [] + build-now here [] + build-now [ []] [] -If there aren't many options or examples to show, they can go in a paragraph of -text. Use double-backticks to put commands in monospaced font, like this:: + Where the optional ```` pair can be used to specify the coordinate bounds + within which ``build-now`` will operate. If they are not specified, + ``build-now`` will scan the entire map. If only one ```` is specified, only + the building at that coordinate is built. - You can use ``cleanowned scattered x`` to dump tattered or abandoned items. + The ```` parameters can either be an ``,,`` triple (e.g. + ``35,12,150``) or the string ``here``, which means the position of the active + game cursor. -If the command takes more than three arguments, format the list as a table -called Usage. The table *only* lists arguments, not full commands. -Input values are specified in angle brackets. Example:: +The second option is to arrange the usage options in a list, with the full command +and arguments in monospaced font. Then indent the next line and describe the effect:: Usage: - :arg1: A simple argument. - :arg2 : Does something based on the input value. - :Very long argument: - Is very specific. + ``build-now []`` + Scan the entire map and build all unsuspended constructions and buildings. + ``build-now here []`` + Build the unsuspended construction or building under the cursor. + ``build-now [ []] []`` + Build all unsuspended constructions within the specified coordinate box. -To demonstrate usage - useful mainly when the syntax is complicated, list the -full command with arguments in monospaced font, then indent the next line and -describe the effect:: + The ```` parameters are specified as... - ``resume all`` - Resumes all suspended constructions. +Note that in both options, the entire commandline syntax is written, including the command itself. +Literal text is written as-is (e.g. the word ``here`` in the above example), and text that +describes the kind of parameter that is being passed (e.g. ``pos`` or ``options``) is enclosed in +angle brackets (``<`` and ``>``). Optional elements are enclosed in square brackets (``[`` and ``]``). -Links ------ +Examples +-------- -If it would be helpful to mention another DFHack command, don't just type the -name - add a hyperlink! Specify the link target in backticks, and it will be -replaced with the corresponding title and linked: e.g. ```autolabor``` -=> `autolabor`. Link targets should be equivalent to the command -described (without file extension), and placed above the heading of that -section like this:: +If the only way to run the command is to type the command itself, then this section is not necessary. +Otherwise, please consider adding a section that shows some real, practical usage examples. For +many users, this will be the **only** section they will read. It is so important that it is a good +idea to include the ``Examples`` section **before** you describe any extended options your command +might take. Write examples for what you expect the popular use cases will be. Also be sure to write +examples showing specific, practical values being used for any parameter that takes a value. - .. _autolabor: +Examples should go in their own subheading with a single dash underline (``--------``). The examples +themselves should be organized in a list, the same as in option 2 for Usage above. Here is an +example Examples section:: - autolabor - ========= + Examples + -------- -Add link targets if you need them, but otherwise plain headings are preferred. -Scripts have link targets created automatically. + ``build-now`` + Completes all unsuspended construction jobs on the map. + ``build-now 37,20,154 here`` + Builds the unsuspended, unconstructed buildings in the box bounded by the coordinate + x=37,y=20,z=154 and the cursor. -Note that the DFHack documentation is configured so that single backticks (with -no prefix or suffix) produce links to internal link targets, such as the -``autolabor`` target shown above. This is different from the reStructuredText -default behavior of rendering such text in italics (as a reference to a title). -For alternative link behaviors, see: +Options +------- -- `The reStructuredText documentation on roles `__ -- `The reStructuredText documentation on external links `__ -- `The Sphinx documentation on roles `__ +The options header should follow the examples, with each option in the same list format as the +examples:: + + Options + ------- + + ``-h``, ``--help`` + Show help text. + ``-l``, ``--quality `` + Set the quality of the architecture for built architected builtings. + ``-q``, ``--quiet`` + Suppress informational output (error messages are still printed). + +Note that for parameters that have both short and long forms, any values that those options +take only need to be specified once (e.g. ````). + +External scripts and plugins +============================ + +Scripts and plugins distributed separately from DFHack's release packages don't have the +opportunity to add their documentation to the rendered HTML or text output. However, these +scripts and plugins can use a different mechanism to at least make their help text available +in-game. + +Note that since help text for external scripts and plugins is not rendered by Sphinx, +it should be written in plain text. Any reStructuredText markup will not be processed. + +For external scripts, the short description comes from a comment on the first line +(the comment marker and extra whitespace is stripped). For Lua, this would look like:: + + -- A short description of my cool script. + +and for Ruby scripts it would look like:: + + # A short description of my cool script. + +The main help text for an external script needs to appear between two markers. For +Lua, these markers are ``[====[`` and ``]====]``, and for Ruby they are ``=begin`` and +``=end``. The documentation standards above still apply to external tools, but there is +no need to include backticks for links or monospaced fonts. Here is a Lua example for an +entire script header:: + + -- Inventory management for adventurers. + -- [====[ + gui/adv-inventory + ================= + Tags: adventure, map + + Allows you to quickly move items between containers. This includes yourself + and any followers you have. + + Usage: + + gui/adv-inventory [] + + Examples: + + gui/adv-inventory + Opens the GUI with nothing preselected + gui/adv-inventory take-all + Opens the GUI with all container items already selected and ready to move + into the adventurer's inventory. + + Options: + + take-all + Starts the GUI with container items pre-selected + give-all + Starts the GUI with your own items pre-selected + ]====] - - ``:doc:`` is useful for linking to another document +For external plugins, help text for provided commands can be passed as the ``usage`` +parameter when registering the commands with the ``PluginCommand`` constructor. There +is currently no way for associating help text with the plugin itself, so any +information about what the plugin does when enabled should be combined into the command +help. Required dependencies ===================== @@ -258,8 +404,8 @@ ways to do this: * On Windows, if you prefer to use the batch scripts, you can run ``generate-msvc-gui.bat`` and set ``BUILD_DOCS`` through the GUI. If you are running another file, such as ``generate-msvc-all.bat``, you will need to edit - it to add the flag. You can also run ``cmake`` on the command line, similar to - other platforms. + the batch script to add the flag. You can also run ``cmake`` on the command line, + similar to other platforms. By default, both HTML and text docs are built by CMake. The generated documentation is stored in ``docs/html`` and ``docs/text`` (respectively) in the From c1a694cb1812eaf30ed3e0453d7bc487f1dd8f1a Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 23 Jul 2022 18:26:40 -0400 Subject: [PATCH 298/854] Improve syntax highlighting in Documentation.rst --- docs/Documentation.rst | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/Documentation.rst b/docs/Documentation.rst index 6be4de37f..50a086cdd 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -94,6 +94,8 @@ For alternative link behaviors, see: Documentation standards ======================= +.. highlight:: rst + Whether you're adding new code or just fixing old documentation (and there's plenty), there are a few important standards for completeness and consistent style. Treat this section as a guide rather than iron law, match the surrounding text, and you'll @@ -129,9 +131,7 @@ each command provided by the script or plugin. For scripts, this will just be th Plugins that do not provide commands (i.e. they can just be enabled for some persistent effect or they just export functionality via a Lua API) don't need any ``:dfhack-keybind:`` lines at all. The first line of the text should then be the short description that will be used for the script -or plugin. For example, documentation for the ``build-now`` script might look like: - -.. code-block:: rst +or plugin. For example, documentation for the ``build-now`` script might look like:: build-now ========= @@ -236,11 +236,15 @@ Note that since help text for external scripts and plugins is not rendered by Sp it should be written in plain text. Any reStructuredText markup will not be processed. For external scripts, the short description comes from a comment on the first line -(the comment marker and extra whitespace is stripped). For Lua, this would look like:: +(the comment marker and extra whitespace is stripped). For Lua, this would look like: + +.. code-block:: lua -- A short description of my cool script. -and for Ruby scripts it would look like:: +and for Ruby scripts it would look like: + +.. code-block:: ruby # A short description of my cool script. From a94f3c3cca434158ffb1cc1b3e09b53d39afa340 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 23 Jul 2022 15:31:56 -0700 Subject: [PATCH 299/854] fix some formatting --- docs/Documentation.rst | 69 ++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/docs/Documentation.rst b/docs/Documentation.rst index 50a086cdd..8f135e2a3 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -5,14 +5,14 @@ DFHack Documentation System ########################### -DFHack documentation, like the file you are reading now, is created as ``.rst`` files, -which are in `reStructuredText (reST) `_ format. -This is a documentation format common in the Python community. It is very +DFHack documentation, like the file you are reading now, is created as a set of +``.rst`` files in `reStructuredText (reST) `_ +format. This is a documentation format common in the Python community. It is very similar in concept - and in syntax - to Markdown, as found on GitHub and many other places. However it is more advanced than Markdown, with more features available when compiled to HTML, such as automatic tables of contents, cross-linking, special external links (forum, wiki, etc) and more. The documentation is compiled by a -Python tool, `Sphinx `_. +Python tool named `Sphinx `_. The DFHack build process will compile and install the documentation so it can be displayed in-game by the `help` and `ls` commands (and any other command or GUI that @@ -22,7 +22,7 @@ downloaded release binary), then you only need to build the docs if you're chang and want to see the changes reflected in your game. You can also build the docs if you just want a local HTML- or text-rendered copy, though -you can always read the `online version `_. +you can always read the `online version `_ too. (Note that even if you do want a local copy, it is certainly not necessary to compile the documentation in order to read it. Like Markdown, reST documents are @@ -37,11 +37,13 @@ Concepts and general guidance The source ``.rst`` files are compiled to HTML for viewing in a browser and to text format for viewing in-game. For in-game help, the help text is read from its installed -location in ``hack/docs`` under the DF directory for in-game display. +location in ``hack/docs`` under the DF directory. -Remember, everything should be documented! If it's not clear *where* a particular thing -should be documented, ask on Discord or in the DFHack thread on Bay12 -- as well as -getting help, you'll be providing valuable feedback that makes it easier for future readers! +When writing documentation, remember that everything should be documented! If it's not +clear *where* a particular thing should be documented, ask on Discord or in the DFHack +thread on Bay12 -- you'll not only be getting help, you'll also be providing valuable +feedback that makes it easier for future contributers to find documentation on how to +write the documentation! Try to keep lines within 80-100 characters, so it's readable in plain text in the terminal - Sphinx (our documentation system) will make sure @@ -105,7 +107,7 @@ Where do I add the help text? ----------------------------- For scripts and plugins that are distributed as part of DFHack, documentation files -should be added to the :source-scripts:`docs` and :source:`docs/plugins` directories, +should be added to the :source-scripts:`scripts/docs ` and :source:`docs/plugins` directories, respectively, in a file named after the script or plugin. For example, a script named ``gui/foobar.lua`` (which provides the ``gui/foobar`` command) should be documented in a file named ``docs/gui/foobar.rst`` in the scripts repo. Similarly, a plugin named @@ -138,8 +140,9 @@ or plugin. For example, documentation for the ``build-now`` script might look li Tags: `tag/fort`, `tag/buildings` :dfhack-keybind:`build-now` - Instantly completes unsuspended building jobs. By default, all constructions - and buildings on the map are completed, but the area of effect is configurable. + Instantly completes unsuspended building jobs. By default, all + constructions and buildings on the map are completed, but the area + of effect is configurable. Usage help ---------- @@ -154,14 +157,15 @@ option is to show usage formats together, with an explanation following the bloc build-now here [] build-now [ []] [] - Where the optional ```` pair can be used to specify the coordinate bounds - within which ``build-now`` will operate. If they are not specified, - ``build-now`` will scan the entire map. If only one ```` is specified, only - the building at that coordinate is built. + Where the optional ```` pair can be used to specify the + coordinate bounds within which ``build-now`` will operate. If they + are not specified, ``build-now`` will scan the entire map. If only + one ```` is specified, only the building at that coordinate + is built. - The ```` parameters can either be an ``,,`` triple (e.g. - ``35,12,150``) or the string ``here``, which means the position of the active - game cursor. + The ```` parameters can either be an ``,,`` triple + (e.g. ``35,12,150``) or the string ``here``, which means the + position of the active game cursor. The second option is to arrange the usage options in a list, with the full command and arguments in monospaced font. Then indent the next line and describe the effect:: @@ -169,11 +173,14 @@ and arguments in monospaced font. Then indent the next line and describe the eff Usage: ``build-now []`` - Scan the entire map and build all unsuspended constructions and buildings. + Scan the entire map and build all unsuspended constructions + and buildings. ``build-now here []`` - Build the unsuspended construction or building under the cursor. + Build the unsuspended construction or building under the + cursor. ``build-now [ []] []`` - Build all unsuspended constructions within the specified coordinate box. + Build all unsuspended constructions within the specified + coordinate box. The ```` parameters are specified as... @@ -202,8 +209,8 @@ example Examples section:: ``build-now`` Completes all unsuspended construction jobs on the map. ``build-now 37,20,154 here`` - Builds the unsuspended, unconstructed buildings in the box bounded by the coordinate - x=37,y=20,z=154 and the cursor. + Builds the unsuspended, unconstructed buildings in the box + bounded by the coordinate x=37,y=20,z=154 and the cursor. Options ------- @@ -217,9 +224,11 @@ examples:: ``-h``, ``--help`` Show help text. ``-l``, ``--quality `` - Set the quality of the architecture for built architected builtings. + Set the quality of the architecture for built architected + builtings. ``-q``, ``--quiet`` - Suppress informational output (error messages are still printed). + Suppress informational output (error messages are still + printed). Note that for parameters that have both short and long forms, any values that those options take only need to be specified once (e.g. ````). @@ -260,8 +269,8 @@ entire script header:: ================= Tags: adventure, map - Allows you to quickly move items between containers. This includes yourself - and any followers you have. + Allows you to quickly move items between containers. This includes + yourself and any followers you have. Usage: @@ -272,8 +281,8 @@ entire script header:: gui/adv-inventory Opens the GUI with nothing preselected gui/adv-inventory take-all - Opens the GUI with all container items already selected and ready to move - into the adventurer's inventory. + Opens the GUI with all container items already selected and + ready to move into the adventurer's inventory. Options: From e4273589e1df7710f866dddfd0b80fce665db63e Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 23 Jul 2022 15:38:35 -0700 Subject: [PATCH 300/854] one fewer char per line so no horiz scrolling --- docs/Documentation.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/Documentation.rst b/docs/Documentation.rst index 8f135e2a3..170a6fd4e 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -141,8 +141,8 @@ or plugin. For example, documentation for the ``build-now`` script might look li :dfhack-keybind:`build-now` Instantly completes unsuspended building jobs. By default, all - constructions and buildings on the map are completed, but the area - of effect is configurable. + constructions and buildings on the map are completed, but the + area of effect is configurable. Usage help ---------- @@ -158,10 +158,10 @@ option is to show usage formats together, with an explanation following the bloc build-now [ []] [] Where the optional ```` pair can be used to specify the - coordinate bounds within which ``build-now`` will operate. If they - are not specified, ``build-now`` will scan the entire map. If only - one ```` is specified, only the building at that coordinate - is built. + coordinate bounds within which ``build-now`` will operate. If + they are not specified, ``build-now`` will scan the entire map. + If only one ```` is specified, only the building at that + coordinate is built. The ```` parameters can either be an ``,,`` triple (e.g. ``35,12,150``) or the string ``here``, which means the @@ -269,8 +269,8 @@ entire script header:: ================= Tags: adventure, map - Allows you to quickly move items between containers. This includes - yourself and any followers you have. + Allows you to quickly move items between containers. This + includes yourself and any followers you have. Usage: From 947889873d379f49e138a1740c3bda32eaeb22f4 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 23 Jul 2022 16:03:40 -0700 Subject: [PATCH 301/854] update all docs I've done so far to new standards --- docs/Documentation.rst | 2 +- docs/builtins/alias.rst | 9 +++--- docs/builtins/cls.rst | 1 - docs/builtins/devel/dump-rpc.rst | 1 - docs/builtins/die.rst | 1 - docs/builtins/disable.rst | 1 - docs/builtins/enable.rst | 5 ++- docs/builtins/fpause.rst | 1 - docs/builtins/help.rst | 1 - docs/builtins/hide.rst | 1 - docs/builtins/keybinding.rst | 15 +++++---- docs/builtins/kill-lua.rst | 1 - docs/builtins/load.rst | 1 - docs/builtins/ls.rst | 22 ++++++------- docs/builtins/plug.rst | 5 ++- docs/builtins/reload.rst | 1 - docs/builtins/sc-script.rst | 13 ++++---- docs/builtins/script.rst | 7 ++--- docs/builtins/show.rst | 1 - docs/builtins/tags.rst | 1 - docs/builtins/type.rst | 1 - docs/builtins/unload.rst | 1 - docs/plugins/3dveins.rst | 1 - docs/plugins/add-spatter.rst | 1 - docs/plugins/autochop.rst | 1 - docs/plugins/autoclothing.rst | 5 ++- docs/plugins/autodump.rst | 17 +++++----- docs/plugins/autofarm.rst | 15 +++++---- docs/plugins/autogems.rst | 5 ++- docs/plugins/autohauler.rst | 23 +++++++------- docs/plugins/autolabor.rst | 41 ++++++++++++------------ docs/plugins/automaterial.rst | 1 - docs/plugins/automelt.rst | 1 - docs/plugins/autotrade.rst | 1 - docs/plugins/blueprint.rst | 53 ++++++++++++++++---------------- docs/plugins/building-hacks.rst | 1 - docs/plugins/buildingplan.rst | 9 +++--- docs/plugins/burrows.rst | 23 +++++++------- docs/plugins/changeitem.rst | 33 ++++++++++---------- docs/plugins/changelayer.rst | 45 +++++++++++++-------------- docs/plugins/changevein.rst | 5 ++- docs/plugins/cleanconst.rst | 1 - docs/plugins/cleaners.rst | 23 +++++++------- docs/plugins/cleanowned.rst | 9 +++--- docs/plugins/command-prompt.rst | 1 - docs/plugins/confirm.rst | 5 ++- docs/plugins/createitem.rst | 16 +++++----- docs/plugins/cursecheck.rst | 37 +++++++++++----------- 48 files changed, 211 insertions(+), 254 deletions(-) diff --git a/docs/Documentation.rst b/docs/Documentation.rst index 170a6fd4e..8908272ca 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -267,7 +267,7 @@ entire script header:: -- [====[ gui/adv-inventory ================= - Tags: adventure, map + Tags: adventure, items Allows you to quickly move items between containers. This includes yourself and any followers you have. diff --git a/docs/builtins/alias.rst b/docs/builtins/alias.rst index 234f8ae4a..96f088798 100644 --- a/docs/builtins/alias.rst +++ b/docs/builtins/alias.rst @@ -1,6 +1,5 @@ alias ===== - Tags: `tag/system` :dfhack-keybind:`alias` @@ -12,14 +11,14 @@ or script. Usage: -- ``alias list`` +``alias list`` Lists all configured aliases -- ``alias add [arguments...]`` +``alias add [arguments...]`` Adds an alias -- ``alias replace [arguments...]`` +``alias replace [arguments...]`` Replaces an existing alias with a new command, or adds the alias if it does not already exist -- ``alias delete `` +``alias delete `` Removes the specified alias Aliases can be given additional arguments when created and invoked, which will diff --git a/docs/builtins/cls.rst b/docs/builtins/cls.rst index 2f5387c7f..a0478c8df 100644 --- a/docs/builtins/cls.rst +++ b/docs/builtins/cls.rst @@ -1,6 +1,5 @@ cls === - Tags: `tag/system` :dfhack-keybind:`cls` diff --git a/docs/builtins/devel/dump-rpc.rst b/docs/builtins/devel/dump-rpc.rst index 2f798c79e..65d4dae57 100644 --- a/docs/builtins/devel/dump-rpc.rst +++ b/docs/builtins/devel/dump-rpc.rst @@ -1,6 +1,5 @@ devel/dump-rpc ============== - Tags: `tag/system` :dfhack-keybind:`devel/dump-rpc` diff --git a/docs/builtins/die.rst b/docs/builtins/die.rst index df53c94bd..54a134433 100644 --- a/docs/builtins/die.rst +++ b/docs/builtins/die.rst @@ -1,6 +1,5 @@ die === - Tags: `tag/system` :dfhack-keybind:`die` diff --git a/docs/builtins/disable.rst b/docs/builtins/disable.rst index d17706486..567e9dbb1 100644 --- a/docs/builtins/disable.rst +++ b/docs/builtins/disable.rst @@ -1,6 +1,5 @@ disable ======= - Tags: `tag/system` :dfhack-keybind:`disable` diff --git a/docs/builtins/enable.rst b/docs/builtins/enable.rst index 1a714f602..b99dd194a 100644 --- a/docs/builtins/enable.rst +++ b/docs/builtins/enable.rst @@ -1,6 +1,5 @@ enable ====== - Tags: `tag/system` :dfhack-keybind:`enable` @@ -25,7 +24,7 @@ Usage:: Examples -------- -- ``enable manipulator`` +``enable manipulator`` Enable the ``manipulator`` plugin. -- ``enable manipulator search`` +``enable manipulator search`` Enable multiple plugins at once. diff --git a/docs/builtins/fpause.rst b/docs/builtins/fpause.rst index f40f6d55c..c43cedfab 100644 --- a/docs/builtins/fpause.rst +++ b/docs/builtins/fpause.rst @@ -1,6 +1,5 @@ fpause ====== - Tags: `tag/system` :dfhack-keybind:`fpause` diff --git a/docs/builtins/help.rst b/docs/builtins/help.rst index 290d62e4c..957a84466 100644 --- a/docs/builtins/help.rst +++ b/docs/builtins/help.rst @@ -1,6 +1,5 @@ help ==== - Tags: `tag/system` :dfhack-keybind:`help` diff --git a/docs/builtins/hide.rst b/docs/builtins/hide.rst index e4f91abaa..de144b637 100644 --- a/docs/builtins/hide.rst +++ b/docs/builtins/hide.rst @@ -1,6 +1,5 @@ hide ==== - Tags: `tag/system` :dfhack-keybind:`hide` diff --git a/docs/builtins/keybinding.rst b/docs/builtins/keybinding.rst index 2ad9e1542..ad759b14c 100644 --- a/docs/builtins/keybinding.rst +++ b/docs/builtins/keybinding.rst @@ -1,6 +1,5 @@ keybinding ========== - Tags: `tag/system` :dfhack-keybind:`keybinding` @@ -14,15 +13,15 @@ Hotkeys can be any combinations of Ctrl/Alt/Shift with A-Z, 0-9, F1-F12, or Usage: -- ``keybinding`` +``keybinding`` Show some useful information, including the current game context. -- ``keybinding list `` +``keybinding list `` List bindings active for the key combination. -- ``keybinding clear [...]`` +``keybinding clear [...]`` Remove bindings for the specified keys. -- ``keybinding add "cmdline" ["cmdline"...]`` +``keybinding add "cmdline" ["cmdline"...]`` Add bindings for the specified key. -- ``keybinding set "cmdline" ["cmdline"...]`` +``keybinding set "cmdline" ["cmdline"...]`` Clear, and then add bindings for the specified key. The ```` parameter above has the following **case-sensitive** syntax:: @@ -53,8 +52,8 @@ Interactive commands like `liquids` cannot be used as hotkeys. Examples -------- -- ``keybinding add Alt-F1 hotkeys`` +``keybinding add Alt-F1 hotkeys`` Bind Alt-F1 to run the `hotkeys` command on any screen at any time. -- ``keybinding add Alt-F@dwarfmode gui/quickfort`` +``keybinding add Alt-F@dwarfmode gui/quickfort`` Bind Alt-F to run `gui/quickfort`, but only when on a screen that shows the main map. diff --git a/docs/builtins/kill-lua.rst b/docs/builtins/kill-lua.rst index 4b93b8734..222795db3 100644 --- a/docs/builtins/kill-lua.rst +++ b/docs/builtins/kill-lua.rst @@ -1,6 +1,5 @@ kill-lua ======== - Tags: `tag/system` :dfhack-keybind:`kill-lua` diff --git a/docs/builtins/load.rst b/docs/builtins/load.rst index 8e51847dd..b7e3db8f3 100644 --- a/docs/builtins/load.rst +++ b/docs/builtins/load.rst @@ -1,6 +1,5 @@ load ==== - Tags: `tag/system` :dfhack-keybind:`load` diff --git a/docs/builtins/ls.rst b/docs/builtins/ls.rst index e68b740c7..3ae3fb3e1 100644 --- a/docs/builtins/ls.rst +++ b/docs/builtins/ls.rst @@ -1,6 +1,5 @@ ls == - Tags: `tag/system` :dfhack-keybind:`ls` @@ -11,23 +10,16 @@ command name. Can also be invoked as ``dir``. Usage: -- ``ls []`` +``ls []`` Lists all available commands and the tags associated with them. -- ``ls []`` +``ls []`` Shows only commands that have the given tag. Use the `tags` command to see the list of available tags. -- ``ls []`` +``ls []`` Shows commands that include the given string. E.g. ``ls quick`` will show all the commands with "quick" in their names. If the string is also the name of a tag, then it will be interpreted as a tag name. -You can also pass some optional parameters to change how ``ls`` behaves: - -- ``--notags`` - Don't print out the tags associated with each command. -- ``--dev`` - Include commands intended for developers and modders. - Examples -------- @@ -36,3 +28,11 @@ Examples - ``ls --dev trigger`` Lists all commands, including developer and modding commands, that match the substring "trigger" + +Options +------- + +``--notags`` + Don't print out the tags associated with each command. +``--dev`` + Include commands intended for developers and modders. diff --git a/docs/builtins/plug.rst b/docs/builtins/plug.rst index 47efe66b3..26699d400 100644 --- a/docs/builtins/plug.rst +++ b/docs/builtins/plug.rst @@ -1,6 +1,5 @@ plug ==== - Tags: `tag/system` :dfhack-keybind:`plug` @@ -9,8 +8,8 @@ Tags: `tag/system` Usage: -- ``plug`` +``plug`` Lists available plugins and whether they are enabled. -- ``plug [ ...]`` +``plug [ ...]`` Shows the commands implemented by the named plugins and whether the plugins are enabled. diff --git a/docs/builtins/reload.rst b/docs/builtins/reload.rst index cde113ac8..4417b5185 100644 --- a/docs/builtins/reload.rst +++ b/docs/builtins/reload.rst @@ -1,6 +1,5 @@ reload ====== - Tags: `tag/system` :dfhack-keybind:`reload` diff --git a/docs/builtins/sc-script.rst b/docs/builtins/sc-script.rst index bcedb610e..e19af0bf9 100644 --- a/docs/builtins/sc-script.rst +++ b/docs/builtins/sc-script.rst @@ -1,6 +1,5 @@ sc-script ========= - Tags: `tag/system` :dfhack-keybind:`sc-script` @@ -11,16 +10,16 @@ dynamically. Usage: -- ``sc-script [help]`` +``sc-script [help]`` Show the list of valid event names. -- ``sc-script list []`` +``sc-script list []`` List the currently registered files for all events or the specified event. -- ``sc-script add|remove [ ...]`` +``sc-script add|remove [ ...]`` Register or unregister a file to be run for the specified event. -Examples --------- +Example +------- -- ``sc-script add SC_MAP_LOADED spawn_extra_monsters.init`` +``sc-script add SC_MAP_LOADED spawn_extra_monsters.init`` Registers the ``spawn_extra_monsters.init`` file to be run whenever a new map is loaded. diff --git a/docs/builtins/script.rst b/docs/builtins/script.rst index ef1aab64a..f15e86bec 100644 --- a/docs/builtins/script.rst +++ b/docs/builtins/script.rst @@ -1,6 +1,5 @@ script ====== - Tags: `tag/system` :dfhack-keybind:`script` @@ -16,9 +15,9 @@ Usage:: script -Examples --------- +Example +------- -- ``script startup.txt`` +``script startup.txt`` Executes the commands in ``startup.txt``, which exists in your DF game directory. diff --git a/docs/builtins/show.rst b/docs/builtins/show.rst index b22d4bab9..a173231d7 100644 --- a/docs/builtins/show.rst +++ b/docs/builtins/show.rst @@ -1,6 +1,5 @@ show ==== - Tags: `tag/system` :dfhack-keybind:`show` diff --git a/docs/builtins/tags.rst b/docs/builtins/tags.rst index b601a73a1..33ac36fe2 100644 --- a/docs/builtins/tags.rst +++ b/docs/builtins/tags.rst @@ -1,6 +1,5 @@ tags ==== - Tags: `tag/system` :dfhack-keybind:`tags` diff --git a/docs/builtins/type.rst b/docs/builtins/type.rst index 755548da9..7c446bc4e 100644 --- a/docs/builtins/type.rst +++ b/docs/builtins/type.rst @@ -1,6 +1,5 @@ type ==== - Tags: `tag/system` :dfhack-keybind:`type` diff --git a/docs/builtins/unload.rst b/docs/builtins/unload.rst index 1fc8c311e..ec385489f 100644 --- a/docs/builtins/unload.rst +++ b/docs/builtins/unload.rst @@ -1,6 +1,5 @@ unload ====== - Tags: `tag/system` :dfhack-keybind:`unload` diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 3c1276b46..9fe83a4b5 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -1,6 +1,5 @@ 3dveins ======= - Tags: :dfhack-keybind:`3dveins` diff --git a/docs/plugins/add-spatter.rst b/docs/plugins/add-spatter.rst index 38a9f91f0..6e5a8ddb7 100644 --- a/docs/plugins/add-spatter.rst +++ b/docs/plugins/add-spatter.rst @@ -1,6 +1,5 @@ add-spatter =========== - Tags: :index:`Make tagged reactions produce contaminants. diff --git a/docs/plugins/autochop.rst b/docs/plugins/autochop.rst index 3a2284aef..6893644db 100644 --- a/docs/plugins/autochop.rst +++ b/docs/plugins/autochop.rst @@ -1,6 +1,5 @@ autochop ======== - Tags: :index:`Auto-harvest trees when low on stockpiled logs. diff --git a/docs/plugins/autoclothing.rst b/docs/plugins/autoclothing.rst index df937c338..bbfe14cc9 100644 --- a/docs/plugins/autoclothing.rst +++ b/docs/plugins/autoclothing.rst @@ -1,6 +1,5 @@ autoclothing ============ - Tags: :dfhack-keybind:`autoclothing` @@ -22,7 +21,7 @@ material and item. Examples -------- -* ``autoclothing cloth "short skirt" 10``: +``autoclothing cloth "short skirt" 10`` Sets the desired number of cloth short skirts available per citizen to 10. -* ``autoclothing cloth dress``: +``autoclothing cloth dress`` Displays the currently set number of cloth dresses chosen per citizen. diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst index a47878c34..b42ed9d85 100644 --- a/docs/plugins/autodump.rst +++ b/docs/plugins/autodump.rst @@ -1,6 +1,5 @@ autodump ======== - Tags: :dfhack-keybind:`autodump` :dfhack-keybind:`autodump-destroy-here` @@ -37,28 +36,28 @@ before the game is resumed, cancels destruction of the item. Options ------- -- ``destroy`` +``destroy`` Destroy instead of dumping. Doesn't require a cursor. If ``autodump`` is called again with this option before the game is resumed, it cancels pending destroy actions. -- ``destroy-here`` +``destroy-here`` :index:`Destroy items marked for dumping under the cursor. ` -- ``visible`` +``visible`` Only process items that are not hidden. -- ``hidden`` +``hidden`` Only process hidden items. -- ``forbidden`` +``forbidden`` Only process forbidden items (default: only unforbidden). Examples -------- -- ``autodump`` +``autodump`` :index:`Teleports items marked for dumping to the cursor position. ` -- ``autodump destroy`` +``autodump destroy`` Destroys all unforbidden items marked for dumping -- ``autodump-destroy-item`` +``autodump-destroy-item`` :index:`Destroys the selected item. ` diff --git a/docs/plugins/autofarm.rst b/docs/plugins/autofarm.rst index 0294b574b..9a2169d65 100644 --- a/docs/plugins/autofarm.rst +++ b/docs/plugins/autofarm.rst @@ -1,6 +1,5 @@ autofarm ======== - Tags: :dfhack-keybind:`autofarm` @@ -12,15 +11,15 @@ threshold for each crop type is configurable. Usage: -- ``enable autofarm`` +``enable autofarm`` Enable the plugin and start managing crop assignment. -- ``autofarm runonce`` +``autofarm runonce`` Updates all farm plots once, without enabling the plugin. -- ``autofarm status`` +``autofarm status`` Prints status information, including any defined thresholds. -- ``autofarm default `` +``autofarm default `` Sets the default threshold. -- ``autofarm threshold [ ...]`` +``autofarm threshold [ ...]`` Sets thresholds of individual plant types. You can find the identifiers for the crop types in your world by running the @@ -31,7 +30,7 @@ following command:: Examples -------- -- ``autofarm default 30`` +``autofarm default 30`` Set the default threshold to 30. -- ``autofarm threshold 150 MUSHROOM_HELMET_PLUMP GRASS_TAIL_PIG`` +``autofarm threshold 150 MUSHROOM_HELMET_PLUMP GRASS_TAIL_PIG`` Set the threshold for Plump Helmets and Pig Tails to 150 diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index bdfd51864..72b850754 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -1,6 +1,5 @@ autogems ======== - Tags: :dfhack-keybind:`autogems-reload` @@ -10,9 +9,9 @@ orders for cutting them at a Jeweler's Workshop. Usage: -- ``enable autogems`` +``enable autogems`` Enables the plugin -- ``autogems-reload`` +``autogems-reload`` :index:`Reloads the autogems configuration file. ` You might need to do this if you have manually modified the contents while the game is diff --git a/docs/plugins/autohauler.rst b/docs/plugins/autohauler.rst index 1dce8a692..82561fa2a 100644 --- a/docs/plugins/autohauler.rst +++ b/docs/plugins/autohauler.rst @@ -1,6 +1,5 @@ autohauler ========== - Tags: :dfhack-keybind:`autohauler` @@ -24,33 +23,33 @@ can be changed by the user. Usage: -- ``enable autohauler`` +``enable autohauler`` Start managing hauling labors. This is normally all you need to do. Autohauler works well on default settings. -- ``autohauler status`` +``autohauler status`` Show autohauler status and status of fort dwarves. -- ``autohauler haulers`` +``autohauler haulers`` Set whether a particular labor should be assigned to haulers. -- ``autohauler allow|forbid`` +``autohauler allow|forbid`` Set whether a particular labor should mark a dwarf as exempt from hauling. By default, only the ``ALCHEMIST`` labor is set to ``forbid``. -- ``autohauler reset-all| reset`` +``autohauler reset-all| reset`` Reset a particular labor (or all labors) to their default haulers/allow/forbid state. -- ``autohauler list`` +``autohauler list`` Show the active configuration for all labors. -- ``autohauler frameskip `` +``autohauler frameskip `` Set the number of frames between runs of autohauler. -- ``autohauler debug`` +``autohauler debug`` In the next cycle, output the state of every dwarf. Examples -------- -- ``autohauler HAUL_STONE haulers`` +``autohauler HAUL_STONE haulers`` Set stone hauling as a hauling labor (this is already the default). -- ``autohauler RECOVER_WOUNDED allow`` +``autohauler RECOVER_WOUNDED allow`` Allow the "Recover wounded" labor to be manually assigned by the player. By default it is automatically given to haulers. -- ``autohauler MINE forbid`` +``autohauler MINE forbid`` Don't assign hauling labors to dwarves with the Mining labor enabled. diff --git a/docs/plugins/autolabor.rst b/docs/plugins/autolabor.rst index 7696efe71..ca249ee19 100644 --- a/docs/plugins/autolabor.rst +++ b/docs/plugins/autolabor.rst @@ -1,6 +1,5 @@ autolabor ========= - Tags: :dfhack-keybind:`autolabor` @@ -54,36 +53,36 @@ dwarfs that meet any of these conditions: We stop assigning dwarves when we reach the maximum allowed. +Examples +-------- + +``autolabor MINE 5`` + Keep at least 5 dwarves with mining enabled. +``autolabor CUT_GEM 1 1`` + Keep exactly 1 dwarf with gemcutting enabled. +``autolabor COOK 1 1 3`` + Keep 1 dwarf with cooking enabled, selected only from the top 3. +``autolabor FEED_WATER_CIVILIANS haulers`` + Have haulers feed and water wounded dwarves. +``autolabor CUTWOOD disable`` + Turn off autolabor for wood cutting. + Advanced usage -------------- -- ``autolabor [] []`` +``autolabor [] []`` Set range of dwarves assigned to a labor, optionally specifying the size of the pool of most skilled dwarves that will ever be considered for this labor. -- ``autolabor haulers`` +``autolabor haulers`` Set a labor to be handled by hauler dwarves. -- ``autolabor disable`` +``autolabor disable`` Turn off autolabor for a specific labor. -- ``autolabor reset-all| reset`` +``autolabor reset-all| reset`` Return a labor (or all labors) to the default handling. -- ``autolabor list`` +``autolabor list`` List current status of all labors. -- ``autolabor status`` +``autolabor status`` Show basic status information. See `autolabor-artisans` for a differently-tuned setup. - -Examples --------- - -- ``autolabor MINE 5`` - Keep at least 5 dwarves with mining enabled. -- ``autolabor CUT_GEM 1 1`` - Keep exactly 1 dwarf with gemcutting enabled. -- ``autolabor COOK 1 1 3`` - Keep 1 dwarf with cooking enabled, selected only from the top 3. -- ``autolabor FEED_WATER_CIVILIANS haulers`` - Have haulers feed and water wounded dwarves. -- ``autolabor CUTWOOD disable`` - Turn off autolabor for wood cutting. diff --git a/docs/plugins/automaterial.rst b/docs/plugins/automaterial.rst index 7f8355b49..3b9a68b7a 100644 --- a/docs/plugins/automaterial.rst +++ b/docs/plugins/automaterial.rst @@ -1,6 +1,5 @@ automaterial ============ - Tags: :index:`Sorts building materials by recent usage. diff --git a/docs/plugins/automelt.rst b/docs/plugins/automelt.rst index b51a431c0..8fe995d23 100644 --- a/docs/plugins/automelt.rst +++ b/docs/plugins/automelt.rst @@ -1,6 +1,5 @@ automelt ======== - Tags: :index:`Quickly designate items to be melted. diff --git a/docs/plugins/autotrade.rst b/docs/plugins/autotrade.rst index ed2d65bc5..68aa2d50d 100644 --- a/docs/plugins/autotrade.rst +++ b/docs/plugins/autotrade.rst @@ -1,6 +1,5 @@ autotrade ========= - Tags: :index:`Quickly designate items to be traded. diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst index 3825de7b2..6a0adfe58 100644 --- a/docs/plugins/blueprint.rst +++ b/docs/plugins/blueprint.rst @@ -1,6 +1,5 @@ blueprint ========= - Tags: :dfhack-keybind:`blueprint` @@ -23,14 +22,14 @@ Usage:: Examples -------- -- ``blueprint gui`` +``blueprint gui`` Runs `gui/blueprint`, the interactive frontend, where all configuration for a ``blueprint`` command can be set visually and interactively. -- ``blueprint 30 40 bedrooms`` +``blueprint 30 40 bedrooms`` Generates blueprints for an area 30 tiles wide by 40 tiles tall, starting from the active cursor on the current z-level. Blueprints are written to ``bedrooms.csv`` in the ``blueprints`` directory. -- ``blueprint 30 40 bedrooms dig --cursor 108,100,150`` +``blueprint 30 40 bedrooms dig --cursor 108,100,150`` Generates only the ``#dig`` blueprint in the ``bedrooms.csv`` file, and the start of the blueprint area is set to a specific value instead of using the in-game cursor position. @@ -38,15 +37,15 @@ Examples Positional parameters --------------------- -- ``width`` +``width`` Width of the area (in tiles) to translate. -- ``height`` +``height`` Height of the area (in tiles) to translate. -- ``depth`` +``depth`` Number of z-levels to translate. Positive numbers go *up* from the cursor and negative numbers go *down*. Defaults to 1 if not specified, indicating that the blueprint should only include the current z-level. -- ``name`` +``name`` Base name for blueprint files created in the ``blueprints`` directory. If no name is specified, "blueprint" is used by default. The string must contain some characters other than numbers so the name won't be confused with the @@ -59,17 +58,17 @@ If you want to generate blueprints only for specific phases, add their names to the commandline, anywhere after the blueprint base name. You can list multiple phases; just separate them with a space. -- ``dig`` +``dig`` Generate quickfort ``#dig`` blueprints for digging natural stone. -- ``carve`` +``carve`` Generate quickfort ``#dig`` blueprints for smoothing and carving. -- ``build`` +``build`` Generate quickfort ``#build`` blueprints for constructions and buildings. -- ``place`` +``place`` Generate quickfort ``#place`` blueprints for placing stockpiles. -- ``zone`` +``zone`` Generate quickfort ``#zone`` blueprints for designating zones. -- ``query`` +``query`` Generate quickfort ``#query`` blueprints for configuring rooms. If no phases are specified, phases are autodetected. For example, a ``#place`` @@ -78,20 +77,20 @@ blueprint will be created only if there are stockpiles in the blueprint area. Options ------- -- ``-c``, ``--cursor ,,`` +``-c``, ``--cursor ,,`` Use the specified map coordinates instead of the current cursor position for the upper left corner of the blueprint range. If this option is specified, then an active game map cursor is not necessary. -- ``-e``, ``--engrave`` +``-e``, ``--engrave`` Record engravings in the ``carve`` phase. If this option is not specified, engravings are ignored. -- ``-f``, ``--format `` - Select the output format of the generated files. See the ``Output formats`` +``-f``, ``--format `` + Select the output format of the generated files. See the `Output formats`_ section below for options. If not specified, the output format defaults to "minimal", which will produce a small, fast ``.csv`` file. -- ``-h``, ``--help`` +``-h``, ``--help`` Show command help text. -- ``-s``, ``--playback-start ,,`` +``-s``, ``--playback-start ,,`` Specify the column and row offsets (relative to the upper-left corner of the blueprint, which is ``1,1``) where the player should put the cursor when the blueprint is played back with `quickfort`, in @@ -100,9 +99,9 @@ Options to surround the parameter string in double quotes: ``"-s10,10,central stairs"`` or ``--playback-start "10,10,central stairs"`` or ``"--playback-start=10,10,central stairs"``. -- ``-t``, ``--splitby `` - Split blueprints into multiple files. See the ``Splitting output into - multiple files`` section below for details. If not specified, defaults to +``-t``, ``--splitby `` + Split blueprints into multiple files. See the `Splitting output into + multiple files`_ section below for details. If not specified, defaults to "none", which will create a standard quickfort `multi-blueprint ` file. @@ -111,10 +110,10 @@ Output formats Here are the values that can be passed to the ``--format`` flag: -- ``minimal`` +``minimal`` Creates ``.csv`` files with minimal file size that are fast to read and write. This is the default. -- ``pretty`` +``pretty`` Makes the blueprints in the ``.csv`` files easier to read and edit with a text editor by adding extra spacing and alignment markers. @@ -123,8 +122,8 @@ Splitting output into multiple files The ``--splitby`` flag can take any of the following values: -- ``none`` +``none`` Writes all blueprints into a single file. This is the standard format for quickfort fortress blueprint bundles and is the default. -- ``phase`` +``phase`` Creates a separate file for each phase. diff --git a/docs/plugins/building-hacks.rst b/docs/plugins/building-hacks.rst index abd6ac4b4..bdae83392 100644 --- a/docs/plugins/building-hacks.rst +++ b/docs/plugins/building-hacks.rst @@ -1,6 +1,5 @@ building-hacks ============== - Tags: :index:`Allows mods to create and manage powered workshops. diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index ac73acd49..6aba86209 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -1,6 +1,5 @@ buildingplan ============ - Tags: :dfhack-keybind:`buildingplan` @@ -36,12 +35,12 @@ The buildingplan plugin has global settings that can be set from the UI :kbd:`b`:kbd:`a`:kbd:`G`). These settings can also be set via the ``buildingplan set`` command. The available settings are: -- ``all_enabled`` (default: false) +``all_enabled`` (default: false) Enable planning mode for all building types. -- ``blocks``, ``boulders``, ``logs``, ``bars`` (defaults: true, true, true, false) +``blocks``, ``boulders``, ``logs``, ``bars`` (defaults: true, true, true, false) Allow blocks, boulders, logs, or bars to be matched for generic "building material" items. -- ``quickfort_mode`` (default: false) +``quickfort_mode`` (default: false) Enable compatibility mode for the legacy Python Quickfort (this setting is not required for DFHack `quickfort`) @@ -51,7 +50,7 @@ save. If you normally embark with some blocks on hand for early workshops, you might want to add this line to your ``dfhack-config/init/onMapLoad.init`` file to -always configure buildingplan to just use blocks for buildlings and +always configure buildingplan to just use blocks for buildings and constructions:: on-new-fortress buildingplan set boulders false; buildingplan set logs false diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst index 6ab26caa2..733f784d0 100644 --- a/docs/plugins/burrows.rst +++ b/docs/plugins/burrows.rst @@ -1,6 +1,5 @@ burrows ======= - Tags: :dfhack-keybind:`burrow` @@ -16,33 +15,33 @@ You can also use the ``burrow`` command to Usage: -- ``enable burrows`` +``enable burrows`` Enable the plugin for the auto-grow feature (see ``burrow enable auto-grow`` below) -- ``burrow enable auto-grow`` +``burrow enable auto-grow`` When a wall inside a burrow with a name ending in '+' is dug out, the burrow will be extended to newly-revealed adjacent walls. This final '+' may be omitted in burrow name args of other ``burrows`` commands. Note that digging 1-wide corridors with the miner inside the burrow is SLOW. Be sure to also run ``enable burrows`` for this feature to work. -- ``burrow disable auto-grow`` +``burrow disable auto-grow`` Disables auto-grow processing. -- ``burrow clear-unit [ ...]`` +``burrow clear-unit [ ...]`` Remove all units from the named burrows. -- ``burrow clear-tiles [ ...]`` +``burrow clear-tiles [ ...]`` Remove all tiles from the named burrows. -- ``burrow set-units target-burrow [ ...]`` +``burrow set-units target-burrow [ ...]`` Clear all units from the target burrow, then add units from the named source burrows. -- ``burrow add-units target-burrow [ ...]`` +``burrow add-units target-burrow [ ...]`` Add units from the source burrows to the target. -- ``burrow remove-units target-burrow [ ...]`` +``burrow remove-units target-burrow [ ...]`` Remove units in source burrows from the target. -- ``burrow set-tiles target-burrow [ ...]`` +``burrow set-tiles target-burrow [ ...]`` Clear target burrow tiles and adds tiles from the names source burrows. -- ``burrow add-tiles target-burrow [ ...]`` +``burrow add-tiles target-burrow [ ...]`` Add tiles from the source burrows to the target. -- ``burrow remove-tiles target-burrow [ ...]`` +``burrow remove-tiles target-burrow [ ...]`` Remove tiles in source burrows from the target. In place of a source burrow, you can use one of the following keywords: diff --git a/docs/plugins/changeitem.rst b/docs/plugins/changeitem.rst index 5dd537a60..93a10cbfa 100644 --- a/docs/plugins/changeitem.rst +++ b/docs/plugins/changeitem.rst @@ -1,8 +1,7 @@ changeitem ========== - Tags: -:dfhack-keybind: +:dfhack-keybind:`changeitem` :index:`Change item material and base quality. ` By default, a change is @@ -14,31 +13,31 @@ refuse to touch. Usage: -- ``changeitem info`` +``changeitem info`` Show details about the selected item. Does not change the item. You can use this command to discover RAW ids for existing items. -- ``changeitem []`` +``changeitem []`` Change the item selected in the ``k`` list or inside a container/inventory. -- ``changeitem here []`` +``changeitem here []`` Change all items at the cursor position. Requires in-game cursor. +Examples +-------- + +``changeitem here m INORGANIC:GRANITE`` + Change material of all stone items under the cursor to granite. +``changeitem q 5`` + Change currently selected item to masterpiece quality. + Options ------- -- ``m``, ``material `` +``m``, ``material `` Change material. Must be followed by valid material RAW id. -- ``s``, ``subtype `` +``s``, ``subtype `` Change subtype. Must be followed by a valid subtype RAW id." -- ``q``, ``quality `` +``q``, ``quality `` Change base quality. Must be followed by number (0-5) with 0 being no quality and 5 being masterpiece quality. -- ``force`` +``force`` Ignore subtypes and force the change to the new material. - -Examples --------- - -``changeitem here m INORGANIC:GRANITE`` - Change material of all stone items under the cursor to granite. -``changeitem q 5`` - Change currently selected item to masterpiece quality. diff --git a/docs/plugins/changelayer.rst b/docs/plugins/changelayer.rst index 76d631cdf..5298cf955 100644 --- a/docs/plugins/changelayer.rst +++ b/docs/plugins/changelayer.rst @@ -1,8 +1,7 @@ changelayer =========== - Tags: -:dfhack-keybind: +:dfhack-keybind:`changelayer` :index:`Change the material of an entire geology layer. ` Note that one @@ -26,27 +25,6 @@ When run without options, ``changelayer`` will: You can use the `probe` command on various tiles around your map to find valid material RAW ids and to get an idea how layers and biomes are distributed. -Options -------- - -- ``all_biomes`` - Change the corresponding geology layer for all biomes on your map. Be aware - that the same geology layer can AND WILL be on different z-levels for - different biomes. -- ``all_layers`` - Change all geology layers on your map (only for the selected biome unless - ``all_biomes`` is also specified). Candy mountain, anyone? Will make your map - quite boring, but tidy. -- ``force`` - Allow changing stone to soil and vice versa. **THIS CAN HAVE WEIRD EFFECTS, - USE WITH CARE AND SAVE FIRST**. Note that soil will not be magically replaced - with stone. You will, however, get a stone floor after digging, so it will - allow the floor to be engraved. Similarly, stone will not be magically - replaced with soil, but you will get a soil floor after digging, so it could - be helpful for creating farm plots on maps with no soil. -- ``verbose`` - Output details about what is being changed. - Examples -------- @@ -71,3 +49,24 @@ Examples weird stuff (flashing tiles, tiles changed all over place etc). Try reverting the changes manually or even better use an older savegame. You did save your game, right? + +Options +------- + +``all_biomes`` + Change the corresponding geology layer for all biomes on your map. Be aware + that the same geology layer can AND WILL be on different z-levels for + different biomes. +``all_layers`` + Change all geology layers on your map (only for the selected biome unless + ``all_biomes`` is also specified). Candy mountain, anyone? Will make your map + quite boring, but tidy. +``force`` + Allow changing stone to soil and vice versa. **THIS CAN HAVE WEIRD EFFECTS, + USE WITH CARE AND SAVE FIRST**. Note that soil will not be magically replaced + with stone. You will, however, get a stone floor after digging, so it will + allow the floor to be engraved. Similarly, stone will not be magically + replaced with soil, but you will get a soil floor after digging, so it could + be helpful for creating farm plots on maps with no soil. +``verbose`` + Output details about what is being changed. diff --git a/docs/plugins/changevein.rst b/docs/plugins/changevein.rst index 7de94a3f0..e23c3f063 100644 --- a/docs/plugins/changevein.rst +++ b/docs/plugins/changevein.rst @@ -1,8 +1,7 @@ changevein ========== - Tags: -:dfhack-keybind: +:dfhack-keybind:`changevein` :index:`Change the material of a mineral inclusion. ` You can change it to @@ -20,5 +19,5 @@ Usage:: Example ------- -- ``changevein NATIVE_PLATINUM`` +``changevein NATIVE_PLATINUM`` Convert vein at cursor position into platinum ore. diff --git a/docs/plugins/cleanconst.rst b/docs/plugins/cleanconst.rst index 286069b63..37416c551 100644 --- a/docs/plugins/cleanconst.rst +++ b/docs/plugins/cleanconst.rst @@ -1,6 +1,5 @@ cleanconst ========== - Tags: :dfhack-keybind:`cleanconst` diff --git a/docs/plugins/cleaners.rst b/docs/plugins/cleaners.rst index 9578bb22a..305bf0db6 100644 --- a/docs/plugins/cleaners.rst +++ b/docs/plugins/cleaners.rst @@ -3,7 +3,6 @@ cleaners ======== - Tags: :dfhack-keybind:`clean` :dfhack-keybind:`spotclean` @@ -28,22 +27,22 @@ includes hostiles, and that cleaning items removes poisons from weapons. if you just want to clean a specific tile but don't want the `clean` command to remove all the glorious blood from your entranceway. +Examples +-------- + +``clean all`` + Clean everything that can be cleaned (except mud and snow). +``clean all mud item snow`` + Removes all spatter, including mud, leaves, and snow from map tiles. + Options ------- When cleaning the map, you can specify extra options for extra cleaning: -- ``mud`` +``mud`` Also remove mud. -- ``item`` +``item`` Also remove item spatter, like fallen leaves and flowers. -- ``snow`` +``snow`` Also remove snow coverings. - -Examples --------- - -- ``clean all`` - Clean everything that can be cleaned (except mud and snow). -- ``clean all mud item snow`` - Removes all spatter, including mud, leaves, and snow from map tiles. diff --git a/docs/plugins/cleanowned.rst b/docs/plugins/cleanowned.rst index e827989c9..c90fe0569 100644 --- a/docs/plugins/cleanowned.rst +++ b/docs/plugins/cleanowned.rst @@ -1,6 +1,5 @@ cleanowned ========== - Tags: :dfhack-keybind:`cleanowned` @@ -21,11 +20,11 @@ anything. You can confiscate additional types of items by adding them to the commandline: -- ``scattered`` +``scattered`` Confiscate/dump all items scattered on the floor. -- ``x`` +``x`` Confiscate/dump items with wear level 'x' (lightly worn) and more. -- ``X`` +``X`` Confiscate/dump items with wear level 'X' (heavily worn) and more. Or you can confiscate all owned items by specifying ``all``. @@ -33,6 +32,6 @@ Or you can confiscate all owned items by specifying ``all``. Example ------- -- ``cleanowned scattered X`` +``cleanowned scattered X`` Confiscate and dump rotten and dropped food, garbage on the floors, and any worn items with 'X' damage and above. diff --git a/docs/plugins/command-prompt.rst b/docs/plugins/command-prompt.rst index 6e3d2a829..3b46e1b33 100644 --- a/docs/plugins/command-prompt.rst +++ b/docs/plugins/command-prompt.rst @@ -1,6 +1,5 @@ command-prompt ============== - Tags: :dfhack-keybind:`command-prompt` diff --git a/docs/plugins/confirm.rst b/docs/plugins/confirm.rst index 25cc04956..aceb1e6d6 100644 --- a/docs/plugins/confirm.rst +++ b/docs/plugins/confirm.rst @@ -1,6 +1,5 @@ confirm ======= - Tags: :dfhack-keybind:`confirm` @@ -11,9 +10,9 @@ case you hit the key accidentally. Usage: -- ``enable confirm``, ``confirm enable all`` +``enable confirm``, ``confirm enable all`` Enable all confirmation options. Replace with ``disable`` to disable all. -- ``confirm enable option1 [option2...]`` +``confirm enable option1 [option2...]`` Enable (or ``disable``) specific confirmation dialogs. When run without parameters, ``confirm`` will report which confirmation dialogs diff --git a/docs/plugins/createitem.rst b/docs/plugins/createitem.rst index 13ecc2e58..b49a85be4 100644 --- a/docs/plugins/createitem.rst +++ b/docs/plugins/createitem.rst @@ -1,6 +1,5 @@ createitem ========== - Tags: :dfhack-keybind:`createitem` @@ -23,14 +22,14 @@ Corpses, body parts, and prepared meals cannot be created using this tool. Usage: -- ``createitem []`` +``createitem []`` Create copies (default is 1) of the specified item made out of the specified material. -- ``createitem inspect`` +``createitem inspect`` Obtain the item and material tokens of an existing item. Its output can be used directly as arguments to ``createitem`` to create new matching items (as long as the item type is supported). -- ``createitem floor|item|building`` +``createitem floor|item|building`` Subsequently created items will be placed on the floor beneath the selected unit's, inside the selected item, or as part of the selected building. @@ -39,14 +38,15 @@ Usage: ``createitem building`` is good for loading traps, but if you use it with workshops, you will have to deconstruct the workshop to access the item. -Examples: +Examples +-------- -- ``createitem GLOVES:ITEM_GLOVES_GAUNTLETS INORGANIC:STEEL 2`` +``createitem GLOVES:ITEM_GLOVES_GAUNTLETS INORGANIC:STEEL 2`` Create 2 pairs of steel gauntlets (that is, 2 left gauntlets and 2 right gauntlets). -- ``createitem WOOD PLANT_MAT:TOWER_CAP:WOOD 100`` +``createitem WOOD PLANT_MAT:TOWER_CAP:WOOD 100`` Create 100 tower-cap logs. -- ``createitem PLANT_GROWTH BILBERRY:FRUIT`` +``createitem PLANT_GROWTH BILBERRY:FRUIT`` Create a single bilberry. For more examples, :wiki:`the wiki `. diff --git a/docs/plugins/cursecheck.rst b/docs/plugins/cursecheck.rst index 79da6d621..17cdacf26 100644 --- a/docs/plugins/cursecheck.rst +++ b/docs/plugins/cursecheck.rst @@ -1,6 +1,5 @@ cursecheck ========== - Tags: :dfhack-keybind:`cursecheck` @@ -22,23 +21,8 @@ Usage:: cursecheck [] -Options: - -- ``detail`` - Print full name, date of birth, date of curse, and some status info (some - vampires might use fake identities in-game, though). -- ``nick`` - Set the type of curse as nickname (does not always show up in-game; some - vamps don't like nicknames). -- ``ids`` - Print the creature and race IDs. -- ``all`` - Include dead and passive cursed creatures (this can result in quite a long - list after having !!FUN!! with necromancers). -- ``verbose`` - Print all curse tags (if you really want to know it all). - -Examples: +Examples +-------- - ``cursecheck`` Display a count of cursed creatures on the map (or under the cursor). @@ -54,3 +38,20 @@ Examples: If you see any living/active creatures with a cursetype of "unknown", then it is most likely a new type of curse introduced by a mod. + +Options +------- + +``detail`` + Print full name, date of birth, date of curse, and some status info (some + vampires might use fake identities in-game, though). +``nick`` + Set the type of curse as nickname (does not always show up in-game; some + vamps don't like nicknames). +``ids`` + Print the creature and race IDs. +``all`` + Include dead and passive cursed creatures (this can result in quite a long + list after having !!FUN!! with necromancers). +``verbose`` + Print all curse tags (if you really want to know it all). From 3c92d4f1956d67cd7b07d62e80aa19c77b9289eb Mon Sep 17 00:00:00 2001 From: Myk Date: Sat, 23 Jul 2022 16:35:40 -0700 Subject: [PATCH 302/854] Fix typo --- library/lua/helpdb.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 8b35b0d25..5e0f49920 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -636,7 +636,7 @@ end function is_builtin(command) ensure_db() return entrydb[command] and - get_db_property(entry_name, 'entry_types')[ENTRY_TYPES.BUILTIN] + get_db_property(command, 'entry_types')[ENTRY_TYPES.BUILTIN] end --------------------------------------------------------------------------- From 2ed93418d2b7400162aa9ab6e6720bfd05af692a Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 23 Jul 2022 21:51:28 -0700 Subject: [PATCH 303/854] separate base command cnts from user command cnts --- data/CMakeLists.txt | 3 +++ .../command_counts.json => data/base_command_counts.json | 0 2 files changed, 3 insertions(+) rename dfhack-config/command_counts.json => data/base_command_counts.json (100%) diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index ea88d4473..412ffa347 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -1,6 +1,9 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/init/ DESTINATION "${DFHACK_DATA_DESTINATION}/init") +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/base_command_counts.json + DESTINATION "${DFHACK_DATA_DESTINATION}/data/base_command_counts.json") + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/quickfort/ DESTINATION "${DFHACK_DATA_DESTINATION}/data/quickfort") diff --git a/dfhack-config/command_counts.json b/data/base_command_counts.json similarity index 100% rename from dfhack-config/command_counts.json rename to data/base_command_counts.json From 5f56d792352c0ee3ca2c1656cd96231c8e46a804 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 23 Jul 2022 21:55:49 -0700 Subject: [PATCH 304/854] move dfhack.history to dfhack-config/dfhack.history --- docs/plugins/debug.rst | 22 ++++++++--------- docs/plugins/deramp.rst | 1 - docs/plugins/dig-now.rst | 23 +++++++++--------- docs/plugins/dig.rst | 52 ++++++++++++++++++++++++++-------------- library/Core.cpp | 6 +++-- 5 files changed, 61 insertions(+), 43 deletions(-) diff --git a/docs/plugins/debug.rst b/docs/plugins/debug.rst index 331e675ab..2b3b20359 100644 --- a/docs/plugins/debug.rst +++ b/docs/plugins/debug.rst @@ -1,6 +1,5 @@ debug ===== - Tags: :dfhack-keybind:`debugfilter` @@ -37,36 +36,37 @@ printing level selection. Usage: -- ``debugfilter category [] []`` +``debugfilter category [] []`` List available debug plugin and category names. If filters aren't givenm then all plugins/categories are matched. This command is a good way to test regex parameters before you pass them to ``set``. -- ``debugfilter filter []`` +``debugfilter filter []`` List active and passive debug print level changes. The optional ``id`` parameter is the id listed as first column in the filter list. If ``id`` is given, then the command shows extended information for the given filter only. -- ``debugfilter set [] [] []`` +``debugfilter set [] [] []`` Create a new debug filter to set category verbosity levels. This filter will not be saved when the DF process exists or the plugin is unloaded. -- ``debugfilter set persistent [] [] []`` +``debugfilter set persistent [] [] []`` Store the filter in the configuration file to until ``unset`` is used to remove it. -- ``debugfilter unset [ ...]`` +``debugfilter unset [ ...]`` Delete a space separated list of filters. -- ``debugfilter disable [ ...]`` +``debugfilter disable [ ...]`` Disable a space separated list of filters but keep it in the filter list. -- ``debugfilter enable [ ...]`` +``debugfilter enable [ ...]`` Enable a space sperate list of filters. -- ``debugfilter header [enable] | [disable] [ ...]`` +``debugfilter header [enable] | [disable] [ ...]`` Control which header metadata is shown along with each log message. Run it without parameters to see the list of configurable elements. Include an ``enable`` or ``disable`` keyword to change whether specific elements are shown. -Examples: +Example +------- -- ``debugfilter set Warning core script`` +``debugfilter set Warning core script`` Hide script execution log messages (e.g. "Loading script: dfhack-config/dfhack.init"), which are normally output at Info verbosity in the "core" plugin with the "script" category. diff --git a/docs/plugins/deramp.rst b/docs/plugins/deramp.rst index 7489e506e..7f4a8ce24 100644 --- a/docs/plugins/deramp.rst +++ b/docs/plugins/deramp.rst @@ -1,6 +1,5 @@ deramp ====== - Tags: :dfhack-keybind:`deramp` diff --git a/docs/plugins/dig-now.rst b/docs/plugins/dig-now.rst index 4628f10f5..cdb21c01a 100644 --- a/docs/plugins/dig-now.rst +++ b/docs/plugins/dig-now.rst @@ -1,6 +1,5 @@ dig-now ======= - Tags: :dfhack-keybind:`dig-now` @@ -33,31 +32,33 @@ Any ```` parameters can either be an ``,,`` triple (e.g. game cursor should be used. You can use the `position` command to get the current cursor position if you need it. -Examples: +Examples +-------- -- ``dig-now`` +``dig-now`` Dig designated tiles according to standard game rules. -- ``dig-now --clean`` +``dig-now --clean`` Dig all designated tiles, but don't generate any boulders, ores, or gems. -- ``dig-now --dump here`` +``dig-now --dump here`` Dig tiles and teleport all generated boulders, ores, and gems to the tile under the game cursor. -Options: +Options +------- -- ``-c``, ``--clean`` +``-c``, ``--clean`` Don't generate any boulders, ores, or gems. Equivalent to ``--percentages 0,0,0,0``. -- ``-d``, ``--dump `` +``-d``, ``--dump `` Dump any generated items at the specified coordinates. If the tile at those coordinates is open space or is a wall, items will be generated on the closest walkable tile below. -- ``-e``, ``--everywhere`` +``-e``, ``--everywhere`` Generate a boulder, ore, or gem for every tile that can produce one. Equivalent to ``--percentages 100,100,100,100``. -- ``-p``, ``--percentages ,,,`` +``-p``, ``--percentages ,,,`` Set item generation percentages for each of the tile categories. The ``vein`` category includes both the large oval clusters and the long stringy mineral veins. Default is ``25,33,100,100``. -- ``-z``, ``--cur-zlevel`` +``-z``, ``--cur-zlevel`` Restricts the bounds to the currently visible z-level. diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst index c4811883b..edb9b8007 100644 --- a/docs/plugins/dig.rst +++ b/docs/plugins/dig.rst @@ -3,17 +3,39 @@ dig === -This plugin makes many automated or complicated dig patterns easy. - -Basic commands: - -:digv: Designate all of the selected vein for digging. -:digvx: Also cross z-levels, digging stairs as needed. Alias for ``digv x``. -:digl: Like ``digv``, for layer stone. Also supports an ``undo`` option - to remove designations, for if you accidentally set 50 levels at once. -:diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. - +Tags: :dfhack-keybind:`digv` +:dfhack-keybind:`digvx` +:dfhack-keybind:`digl` +:dfhack-keybind:`diglx` +:dfhack-keybind:`digcircle` +:dfhack-keybind:`digtype` +:dfhack-keybind:`digexp` + +Make complicated dig patterns easy. + +Usage: + +``digv [x] [-p]`` + Designate all of the selected vein for digging. +``digvx [-p]`` + Also cross z-levels, digging stairs as needed. Alias for ``digv x``. +``digl [x] [undo] [-p]`` + Like ``digv``, for layer stone. If ``undo`` is specified, removes the + layer designation instead (for if you accidentally set 50 levels at once). +``diglx [-p]`` + Also cross z-levels, digging stairs as needed. Alias for ``digl x``. +``digcircle [] [] [] [] [-p]`` + Designate circles. The diameter is the number of tiles across the center of + the circle that you want to dig. See the `digcircle`_ section below for an + explanation of the options. +``digtype [] +For every tile on the map of the same vein type as the selected tile, +this command designates it to have the same designation as the +selected tile. If the selected tile has no designation, they will be +dig designated. + +``digexp [] [] .. note:: @@ -24,15 +46,9 @@ Basic commands: digcircle ========= -A command for easy designation of filled and hollow circles. -It has several types of options. - -Shape: - -:hollow: Set the circle to hollow (default) -:filled: Set the circle to filled -:#: Diameter in tiles (default = 0, does nothing) + Designate filled or hollow circles. If neither ``hollow`` nor ``filled`` + is specified, the default is ``hollow``. The diameter is the number of tiles Action: :set: Set designation (default) diff --git a/library/Core.cpp b/library/Core.cpp index 72ddc33aa..a926391d9 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1311,12 +1311,14 @@ void fInitthread(void * iodata) // A thread function... for the interactive console. void fIOthread(void * iodata) { + static const char * HISTORY_FILE = "dfhack-config/dfhack.history"; + IODATA * iod = ((IODATA*) iodata); Core * core = iod->core; PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr; CommandHistory main_history; - main_history.load("dfhack.history"); + main_history.load(HISTORY_FILE); Console & con = core->getConsole(); if (plug_mgr == 0) @@ -1357,7 +1359,7 @@ void fIOthread(void * iodata) { // a proper, non-empty command was entered main_history.add(command); - main_history.save("dfhack.history"); + main_history.save(HISTORY_FILE); } auto rv = core->runCommand(con, command); From f9d4781cbcb40c70badf78e57d7147e550238b8a Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 23 Jul 2022 22:12:34 -0700 Subject: [PATCH 305/854] use dfhack-config/lua.history instead of lua.history --- library/LuaTools.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index cef46c053..fea90b394 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1145,7 +1145,7 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, return false; if (!hfile) - hfile = "lua.history"; + hfile = "dfhack-config/lua.history"; if (!prompt) prompt = "lua"; From d1f690baa51afb0cdb20707f68055dffb88b68ca Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 23 Jul 2022 22:12:58 -0700 Subject: [PATCH 306/854] move liquids.history to dfhack-config/liquids.history --- plugins/liquids.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 5dd64812f..76c098c8a 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -55,6 +55,7 @@ using namespace df::enums; DFHACK_PLUGIN("liquids"); REQUIRE_GLOBAL(world); +static const char * HISTORY_FILE = "dfhack-config/liquids.history"; CommandHistory liquids_hist; command_result df_liquids (color_ostream &out, vector & parameters); @@ -62,7 +63,7 @@ command_result df_liquids_here (color_ostream &out, vector & parameters DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - liquids_hist.load("liquids.history"); + liquids_hist.load(HISTORY_FILE); commands.push_back(PluginCommand( "liquids", "Place magma, water or obsidian.", df_liquids, true, @@ -80,7 +81,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Sat, 23 Jul 2022 22:13:19 -0700 Subject: [PATCH 307/854] move tiletypes.history to dfhack-config/tiletypes.history --- plugins/tiletypes.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index 7f39ddd4e..b8bbc8d04 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -74,7 +74,7 @@ static const struct_field_info tiletypes_options_fields[] = { }; struct_identity tiletypes_options::_identity(sizeof(tiletypes_options), &df::allocator_fn, NULL, "tiletypes_options", NULL, tiletypes_options_fields); - +static const char * HISTORY_FILE = "dfhack-config/tiletypes.history"; CommandHistory tiletypes_hist; command_result df_tiletypes (color_ostream &out, vector & parameters); @@ -84,7 +84,7 @@ command_result df_tiletypes_here_point (color_ostream &out, vector & pa DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - tiletypes_hist.load("tiletypes.history"); + tiletypes_hist.load(HISTORY_FILE); commands.push_back(PluginCommand("tiletypes", "Paint map tiles freely, similar to liquids.", df_tiletypes, true)); commands.push_back(PluginCommand("tiletypes-command", "Run tiletypes commands (seperated by ' ; ')", df_tiletypes_command)); commands.push_back(PluginCommand("tiletypes-here", "Repeat tiletypes command at cursor (with brush)", df_tiletypes_here)); @@ -94,7 +94,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Sat, 23 Jul 2022 22:40:33 -0700 Subject: [PATCH 308/854] update docs for dig --- docs/plugins/dig.rst | 177 +++++++++++++++++++++++++------------------ 1 file changed, 103 insertions(+), 74 deletions(-) diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst index edb9b8007..941172ef7 100644 --- a/docs/plugins/dig.rst +++ b/docs/plugins/dig.rst @@ -27,101 +27,130 @@ Usage: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. ``digcircle [] [] [] [] [-p]`` Designate circles. The diameter is the number of tiles across the center of - the circle that you want to dig. See the `digcircle`_ section below for an - explanation of the options. -``digtype [] -For every tile on the map of the same vein type as the selected tile, -this command designates it to have the same designation as the -selected tile. If the selected tile has no designation, they will be -dig designated. - -``digexp [] [] - -.. note:: - - All commands implemented by the `dig` plugin (listed by ``ls dig``) support - specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, - where ``#`` is a number from 1 to 7. If a priority is not specified, the - priority selected in-game is used as the default. + the circle that you want to dig. See the `digcircle`_ section below for + options. +``digtype [] [-p]`` + Designate all vein tiles of the selected type. See the `digtype`_ section + below for options. +``digexp [] [] [-p]`` + Designate dig patterns for exploratory mining. See the `digexp`_ section + below for options + +All commands support specifying the priority of the dig designations with +``-p``, where the number is from 1 to 7. If a priority is not specified, +the priority selected in-game is used as the default. + +Examples +-------- + +``digcircle filled 3`` + Dig a filled circle with a diameter of 3 tiles. +``digcircle`` + Do it again (previous parameters are reused). +``expdig diag5 hidden`` + Designate the diagonal 5 pattern over all hidden tiles on the current + z-level. +``expdig ladder designated`` + Take existing designations on the current z-level and replace them with the + ladder pattern. +``expdig`` + Do it again (previous parameters are reused). digcircle -========= +--------- - Designate filled or hollow circles. If neither ``hollow`` nor ``filled`` - is specified, the default is ``hollow``. The diameter is the number of tiles -Action: +The ``digcircle`` command can accept up to one option of each type below. -:set: Set designation (default) -:unset: Unset current designation -:invert: Invert designations already present +Solidity options: -Designation types: +``hollow`` + Designates hollow circles (default). +``filled`` + Designates filled circles. -:dig: Normal digging designation (default) -:ramp: Ramp digging -:ustair: Staircase up -:dstair: Staircase down -:xstair: Staircase up/down -:chan: Dig channel +Action options: -After you have set the options, the command called with no options -repeats with the last selected parameters. +``set`` + Set designation (default). +``unset`` + Unset current designation. +``invert`` + Invert designations already present. -Examples: +Designation options: -``digcircle filled 3`` - Dig a filled circle with diameter = 3. -``digcircle`` - Do it again. +``dig`` + Normal digging designation (default). +``ramp`` + Dig ramps. +``ustair`` + Dig up staircases. +``dstair`` + Dig down staircases. +``xstair`` + Dig up/down staircases. +``chan`` + Dig channels. + +After you have set the options, the command called with no options repeats with +the last selected parameters. digtype -======= -For every tile on the map of the same vein type as the selected tile, -this command designates it to have the same designation as the -selected tile. If the selected tile has no designation, they will be -dig designated. -If an argument is given, the designation of the selected tile is -ignored, and all appropriate tiles are set to the specified -designation. - -Options: - -:dig: -:channel: -:ramp: -:updown: up/down stairs -:up: up stairs -:down: down stairs -:clear: clear designation +------- + +For every tile on the map of the same vein type as the selected tile, this +command designates it to have the same designation as the selected tile. If the +selected tile has no designation, they will be dig designated. + +If an argument is given, the designation of the selected tile is ignored, and +all appropriate tiles are set to the specified designation. + +Designation options: + +``dig`` + Normal digging designation. +``channel`` + Dig channels. +``ramp`` + Dig ramps. +``updown`` + Dig up/down staircases. +``up`` + Dig up staircases. +``down`` + Dig down staircases. +``clear`` + Clear any designations. digexp -====== +------ + This command is for :wiki:`exploratory mining `. There are two variables that can be set: pattern and filter. Patterns: -:diag5: diagonals separated by 5 tiles -:diag5r: diag5 rotated 90 degrees -:ladder: A 'ladder' pattern -:ladderr: ladder rotated 90 degrees -:clear: Just remove all dig designations -:cross: A cross, exactly in the middle of the map. +``diag5`` + Diagonals separated by 5 tiles. +``diag5r`` + The diag5 pattern rotated 90 degrees. +``ladder`` + A 'ladder' pattern. +``ladderr`` + The ladder pattern rotated 90 degrees. +``cross`` + A cross, exactly in the middle of the map. +``clear`` + Just remove all dig designations. Filters: -:all: designate whole z-level -:hidden: designate only hidden tiles of z-level (default) -:designated: Take current designation and apply pattern to it. +``hidden`` + Designate only hidden tiles of z-level (default) +``all`` + Designate the whole z-level. +``designated`` + Take current designation and apply the selected pattern to it. After you have a pattern set, you can use ``expdig`` to apply it again. - -Examples: - -``expdig diag5 hidden`` - Designate the diagonal 5 patter over all hidden tiles -``expdig`` - Apply last used pattern and filter -``expdig ladder designated`` - Take current designations and replace them with the ladder pattern From a567665ab29446e00676b0757752eaeb53b93374 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Jul 2022 05:41:40 +0000 Subject: [PATCH 309/854] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- library/Core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index a926391d9..facd02823 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1312,7 +1312,7 @@ void fInitthread(void * iodata) void fIOthread(void * iodata) { static const char * HISTORY_FILE = "dfhack-config/dfhack.history"; - + IODATA * iod = ((IODATA*) iodata); Core * core = iod->core; PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr; From ca06d1d9c55531ece4aa8527ac2e101706e4c719 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 24 Jul 2022 16:08:35 -0700 Subject: [PATCH 310/854] update docs for dig (again) --- docs/plugins/dig.rst | 37 ++++++++++++++--------- plugins/dig.cpp | 70 ++++++++++++++++++-------------------------- 2 files changed, 51 insertions(+), 56 deletions(-) diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst index 941172ef7..8fbf1a910 100644 --- a/docs/plugins/dig.rst +++ b/docs/plugins/dig.rst @@ -17,24 +17,33 @@ Make complicated dig patterns easy. Usage: ``digv [x] [-p]`` - Designate all of the selected vein for digging. + :index:`Designate all of the selected vein for digging. + ` ``digvx [-p]`` - Also cross z-levels, digging stairs as needed. Alias for ``digv x``. + :index:`Dig a vein across z-levels, digging stairs as needed. + ` + This is an alias for ``digv x``. ``digl [x] [undo] [-p]`` - Like ``digv``, for layer stone. If ``undo`` is specified, removes the - layer designation instead (for if you accidentally set 50 levels at once). + :index:`Dig all of the selected layer stone. + ` If ``undo`` is specified, + removes the designation instead (for if you accidentally set 50 levels at + once). ``diglx [-p]`` - Also cross z-levels, digging stairs as needed. Alias for ``digl x``. + :index:`Dig layer stone across z-levels, digging stairs as needed. + ` This + is an alias for ``digl x``. ``digcircle [] [] [] [] [-p]`` - Designate circles. The diameter is the number of tiles across the center of - the circle that you want to dig. See the `digcircle`_ section below for - options. + :index:`Designate circles. ` The diameter + is the number of tiles across the center of the circle that you want to dig. + See the `digcircle`_ section below for options. ``digtype [] [-p]`` - Designate all vein tiles of the selected type. See the `digtype`_ section - below for options. + :index:`Designate all vein tiles of the selected type. + ` See the `digtype`_ + section below for options. ``digexp [] [] [-p]`` - Designate dig patterns for exploratory mining. See the `digexp`_ section - below for options + :index:`Designate dig patterns for exploratory mining. + ` See the `digexp`_ + section below for options All commands support specifying the priority of the dig designations with ``-p``, where the number is from 1 to 7. If a priority is not specified, @@ -43,8 +52,8 @@ the priority selected in-game is used as the default. Examples -------- -``digcircle filled 3`` - Dig a filled circle with a diameter of 3 tiles. +``digcircle filled 3 -p2`` + Dig a filled circle with a diameter of 3 tiles at dig priority 2. ``digcircle`` Do it again (previous parameters are reused). ``expdig diag5 hidden`` diff --git a/plugins/dig.cpp b/plugins/dig.cpp index 6c33f206a..ddc2cd97e 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -28,7 +28,6 @@ command_result digv (color_ostream &out, vector & parameters); command_result digvx (color_ostream &out, vector & parameters); command_result digl (color_ostream &out, vector & parameters); command_result diglx (color_ostream &out, vector & parameters); -command_result digauto (color_ostream &out, vector & parameters); command_result digexp (color_ostream &out, vector & parameters); command_result digcircle (color_ostream &out, vector & parameters); command_result digtype (color_ostream &out, vector & parameters); @@ -40,44 +39,37 @@ REQUIRE_GLOBAL(world); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "digv","Dig a whole vein.",digv,Gui::cursor_hotkey, - " Designates a whole vein under the cursor for digging.\n" - "Options:\n" - " x - follow veins through z-levels with stairs.\n" - )); + "digv", + "Dig a whole vein.", + digv, + Gui::cursor_hotkey)); commands.push_back(PluginCommand( - "digvx","Dig a whole vein, following through z-levels.",digvx,Gui::cursor_hotkey, - " Designates a whole vein under the cursor for digging.\n" - " Also follows the vein between z-levels with stairs, like 'digv x' would.\n" - )); + "digvx", + "Dig a whole vein, following through z-levels.", + digvx, + Gui::cursor_hotkey)); commands.push_back(PluginCommand( - "digl","Dig layerstone.",digl,Gui::cursor_hotkey, - " Designates layerstone under the cursor for digging.\n" - " Veins will not be touched.\n" - "Options:\n" - " x - follow layer through z-levels with stairs.\n" - " undo - clear designation (can be used together with 'x').\n" - )); + "digl", + "Dig layerstone.", + digl, + Gui::cursor_hotkey)); commands.push_back(PluginCommand( - "diglx","Dig layerstone, following through z-levels.",diglx,Gui::cursor_hotkey, - " Designates layerstone under the cursor for digging.\n" - " Also follows the stone between z-levels with stairs, like 'digl x' would.\n" - )); - commands.push_back(PluginCommand("digexp","Select or designate an exploratory pattern.",digexp)); - commands.push_back(PluginCommand("digcircle","Dig designate a circle (filled or hollow)",digcircle)); - //commands.push_back(PluginCommand("digauto","Mark a tile for continuous digging.",autodig)); - commands.push_back(PluginCommand("digtype", "Dig all veins of a given type.", digtype,Gui::cursor_hotkey, - "For every tile on the map of the same vein type as the selected tile, this command designates it to have the same designation as the selected tile. If the selected tile has no designation, they will be dig designated.\n" - "If an argument is given, the designation of the selected tile is ignored, and all appropriate tiles are set to the specified designation.\n" - "Options:\n" - " dig\n" - " channel\n" - " ramp\n" - " updown - up/down stairs\n" - " up - up stairs\n" - " down - down stairs\n" - " clear - clear designation\n" - )); + "diglx", + "Dig layerstone, following through z-levels.", + diglx, + Gui::cursor_hotkey)); + commands.push_back(PluginCommand( + "digexp", + "Select or designate an exploratory pattern.", + digexp)); + commands.push_back(PluginCommand( + "digcircle", + "Dig designate a circle (filled or hollow)", + digcircle)); + commands.push_back(PluginCommand( + "digtype", + "Dig all veins of a given type.", + digtype,Gui::cursor_hotkey)); return CR_OK; } @@ -1420,12 +1412,6 @@ command_result digl (color_ostream &out, vector & parameters) return CR_OK; } - -command_result digauto (color_ostream &out, vector & parameters) -{ - return CR_NOT_IMPLEMENTED; -} - command_result digtype (color_ostream &out, vector & parameters) { //mostly copy-pasted from digv From 60b599865c664fe065d875f20874b55b111d7a11 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 24 Jul 2022 17:01:50 -0700 Subject: [PATCH 311/854] update docs for digFlood --- docs/plugins/digFlood.rst | 46 ++++++++++++++++++++++++++++----------- plugins/digFlood.cpp | 23 +++----------------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/docs/plugins/digFlood.rst b/docs/plugins/digFlood.rst index dd957feb2..cb8018145 100644 --- a/docs/plugins/digFlood.rst +++ b/docs/plugins/digFlood.rst @@ -1,18 +1,38 @@ digFlood ======== -Automatically digs out specified veins as they are discovered. It runs once -every time a dwarf finishes a dig job. It will only dig out appropriate tiles -that are adjacent to the finished dig job. To add a vein type, use ``digFlood 1 [type]``. -This will also enable the plugin. To remove a vein type, use ``digFlood 0 [type] 1`` -to disable, then remove, then re-enable. +Tags: +:dfhack-keybind:`digFlood` + +:index:`Digs out veins as they are discovered. +` It will only dig out +appropriate tiles that are adjacent to a just-finished dig job, so if you want +to autodig a vein that has already been discovered, you may need to manually +designate one tile of the tile for digging to get started. Usage: -:help digflood: detailed help message -:digFlood 0: disable the plugin -:digFlood 1: enable the plugin -:digFlood 0 MICROCLINE COAL_BITUMINOUS 1: - disable plugin, remove microcline and bituminous coal from monitoring, then re-enable the plugin -:digFlood CLEAR: remove all inorganics from monitoring -:digFlood digAll1: ignore the monitor list and dig any vein -:digFlood digAll0: disable digAll mode +``enable digflood`` + Enable the plugin. +``digflood 1 [ ...]`` + Start monitoring for the specified vein types. +``digFlood 0 [ ...] 1`` + Stop monitoring for the specified vein types. Note the required ``1`` at the + end. +``digFlood CLEAR`` + Remove all inorganics from monitoring. +``digFlood digAll1`` + Ignore the monitor list and dig any vein. +``digFlood digAll0`` + Disable digAll mode. + +You can get the list of valid vein types with this command:: + + lua "for i,mat in ipairs(df.global.world.raws.inorganics) do if mat.material.flags.IS_STONE and not mat.material.flags.NO_STONE_STOCKPILE then print(i, mat.id) end end" + +Examples +-------- + +``digFlood 1 MICROCLINE COAL_BITUMINOUS`` + Automatically dig microcline and bituminous coal veins. +``digFlood 0 MICROCLINE 1`` + Stop automatically digging microcline. diff --git a/plugins/digFlood.cpp b/plugins/digFlood.cpp index 459a12c11..7370d3fe5 100644 --- a/plugins/digFlood.cpp +++ b/plugins/digFlood.cpp @@ -51,26 +51,9 @@ DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "digFlood", "Automatically dig out veins as you discover them.", - digFlood, false, - "Example:\n" - " digFlood 0\n" - " disable plugin\n" - " digFlood 1\n" - " enable plugin\n" - " digFlood 0 MICROCLINE COAL_BITUMINOUS 1\n" - " disable plugin and remove microcline and bituminous coal from being monitored, then re-enable plugin\n" - " digFlood 1 MICROCLINE 0 COAL_BITUMINOUS 1\n" - " do monitor microcline, don't monitor COAL_BITUMINOUS, then enable plugin\n" - " digFlood CLEAR\n" - " remove all inorganics from monitoring\n" - " digFlood digAll1\n" - " enable digAll mode: dig any vein, regardless of the monitor list\n" - " digFlood digAll0\n" - " disable digAll mode\n" - "\n" - "Note that while order matters, multiple commands can be sequenced in one line. It is recommended to alter your save-specific regionX/raw/onLoad.init or global onLoadWorld.init file so that you won't have to type in every mineral type you want to dig every time you start the game. Material names are case sensitive.\n" - )); + "digFlood", + "Automatically dig out veins as you discover them.", + digFlood)); return CR_OK; } From b2ca3cb19439aaaed531b48f4b21943a54fff174 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 24 Jul 2022 20:12:04 -0700 Subject: [PATCH 312/854] update docs for diggingInvaders --- docs/plugins/diggingInvaders.rst | 83 ++++++++++++++------- plugins/diggingInvaders/diggingInvaders.cpp | 25 +------ 2 files changed, 60 insertions(+), 48 deletions(-) diff --git a/docs/plugins/diggingInvaders.rst b/docs/plugins/diggingInvaders.rst index 09a22c07a..9bca23163 100644 --- a/docs/plugins/diggingInvaders.rst +++ b/docs/plugins/diggingInvaders.rst @@ -1,28 +1,59 @@ diggingInvaders =============== -Makes invaders dig or destroy constructions to get to your dwarves. - -To enable/disable the pluging, use: ``diggingInvaders (1|enable)|(0|disable)`` - -Basic usage: - -:add GOBLIN: registers the race GOBLIN as a digging invader. Case-sensitive. -:remove GOBLIN: unregisters the race GOBLIN as a digging invader. Case-sensitive. -:now: makes invaders try to dig now, if plugin is enabled -:clear: clears all digging invader races -:edgesPerTick n: makes the pathfinding algorithm work on at most n edges per tick. - Set to 0 or lower to make it unlimited. - -You can also use ``diggingInvaders setCost (race) (action) n`` to set the -pathing cost of particular action, or ``setDelay`` to set how long it takes. -Costs and delays are per-tile, and the table shows default values. - -============================== ======= ====== ================================= -Action Cost Delay Notes -============================== ======= ====== ================================= -``walk`` 1 0 base cost in the path algorithm -``destroyBuilding`` 2 1,000 delay adds to the job_completion_timer of destroy building jobs that are assigned to invaders -``dig`` 10,000 1,000 digging soil or natural stone -``destroyRoughConstruction`` 1,000 1,000 constructions made from boulders -``destroySmoothConstruction`` 100 100 constructions made from blocks or bars -============================== ======= ====== ================================= +Tags: +:dfhack-keybind:`diggingInvaders` + +:index:`Invaders dig and destroy to get to your dwarves. +` + +Usage: + +``enable diggingInvaders`` + Enable the plugin. +``diggingInvaders add `` + Register the specified race as a digging invader. +``diggingInvaders remove `` + Unregisters the specified race as a digging invader. +``diggingInvaders now`` + Makes invaders try to dig now (if the plugin is enabled). +``diggingInvaders clear`` + Clears the registry of digging invader races. +``diggingInvaders edgesPerTick `` + Makes the pathfinding algorithm work on at most n edges per tick. Set to 0 + or lower to make it unlimited. +``diggingInvaders setCost `` + Set the pathing cost per tile for a particular action. This determines what + invaders consider to be the shortest path to their target. +``diggingInvaders setDelay `` + Set the time cost (in ticks) for performing a particular action. This + determines how long it takes for invaders to get to their target. + +Note that the race is case-sensitive. You can get a list of races for your world +with this command:: + + devel/query --table df.global.world.raws.creatures.all --search creature_id --maxdepth 1 --maxlength 5000 + +but in general, the race is what you'd expect, just capitalized (e.g. ``GOBLIN`` +or ``ELF``). + +Actions: +``walk`` + Default cost: 1, default delay: 0. This is the base cost for the pathing + algorithm. +``destroyBuilding`` + Default cost: 2, default delay: 1,000, +``dig`` + Default cost: 10,000, default delay: 1,000. This is for digging soil or + natural stone. +``destroyRoughConstruction`` + Default cost: 1,000, default delay: 1,000. This is for destroying + constructions made from boulders. +``destroySmoothConstruction`` + Default cost: 100, default delay: 100. This is for destroying constructions + made from blocks or bars. + +Example +------- + +``diggingInvaders add GOBLIN`` + Registers members of the GOBLIN race as a digging invader. diff --git a/plugins/diggingInvaders/diggingInvaders.cpp b/plugins/diggingInvaders/diggingInvaders.cpp index 573c85320..457a00e50 100644 --- a/plugins/diggingInvaders/diggingInvaders.cpp +++ b/plugins/diggingInvaders/diggingInvaders.cpp @@ -120,28 +120,9 @@ static int32_t jobDelayDefault[] = { DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "diggingInvaders", "Makes invaders dig to your dwarves.", - diggingInvadersCommand, false, /* true means that the command can't be used from non-interactive user interface */ - " diggingInvaders 0\n disables the plugin\n" - " diggingInvaders 1\n enables the plugin\n" - " diggingInvaders enable\n enables the plugin\n" - " diggingInvaders disable\n disables the plugin\n" - " diggingInvaders add GOBLIN\n registers the race GOBLIN as a digging invader. Case-sensitive.\n" - " diggingInvaders remove GOBLIN\n unregisters the race GOBLIN as a digging invader. Case-sensitive.\n" - " diggingInvaders setCost GOBLIN walk n\n sets the walk cost in the path algorithm for the race GOBLIN\n" - " diggingInvaders setCost GOBLIN destroyBuilding n\n" - " diggingInvaders setCost GOBLIN dig n\n" - " diggingInvaders setCost GOBLIN destroyRoughConstruction n\n rough constructions are made from boulders\n" - " diggingInvaders setCost GOBLIN destroySmoothConstruction n\n smooth constructions are made from blocks or bars instead of boulders\n" - " diggingInvaders setDelay GOBLIN destroyBuilding n\n adds to the job_completion_timer of destroy building jobs that are assigned to invaders\n" - " diggingInvaders setDelay GOBLIN dig n\n" - " diggingInvaders setDelay GOBLIN destroyRoughConstruction n\n" - " diggingInvaders setDelay GOBLIN destroySmoothConstruction n\n" - " diggingInvaders now\n makes invaders try to dig now, if plugin is enabled\n" - " diggingInvaders clear\n clears all digging invader races\n" - " diggingInvaders edgesPerTick n\n makes the pathfinding algorithm work on at most n edges per tick. Set to 0 or lower to make it unlimited." -// " diggingInvaders\n Makes invaders try to dig now.\n" - )); + "diggingInvaders", + "Makes invaders dig to your dwarves.", + diggingInvadersCommand)); //*df::global::debug_showambush = true; return CR_OK; From d624e9167304c65f6daf096856f9de11af51b895 Mon Sep 17 00:00:00 2001 From: Myk Date: Sun, 24 Jul 2022 20:59:24 -0700 Subject: [PATCH 313/854] Fix missing newline --- docs/plugins/diggingInvaders.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/plugins/diggingInvaders.rst b/docs/plugins/diggingInvaders.rst index 9bca23163..d2fada0d4 100644 --- a/docs/plugins/diggingInvaders.rst +++ b/docs/plugins/diggingInvaders.rst @@ -37,6 +37,7 @@ but in general, the race is what you'd expect, just capitalized (e.g. ``GOBLIN`` or ``ELF``). Actions: + ``walk`` Default cost: 1, default delay: 0. This is the base cost for the pathing algorithm. From ac11cde2130b587a586ea8ee8566d45f384a41a3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 24 Jul 2022 23:08:44 -0700 Subject: [PATCH 314/854] update docs for dwarfmonitor --- docs/plugins/dwarfmonitor.rst | 65 +++++++++++++++++++++-------------- plugins/dwarfmonitor.cpp | 17 ++------- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/docs/plugins/dwarfmonitor.rst b/docs/plugins/dwarfmonitor.rst index cbe3e8bdc..295377c2d 100644 --- a/docs/plugins/dwarfmonitor.rst +++ b/docs/plugins/dwarfmonitor.rst @@ -1,35 +1,48 @@ dwarfmonitor ============ -Records dwarf activity to measure fort efficiency. - -Options: - -:enable : Start monitoring ``mode``. ``mode`` can be "work", "misery", - "weather", or "all". This will enable all corresponding widgets, - if applicable. -:disable : Stop monitoring ``mode``, and disable corresponding widgets, if applicable. -:stats: Show statistics summary -:prefs: Show dwarf preferences summary -:reload: Reload configuration file (``dfhack-config/dwarfmonitor.json``) - +Tags: :dfhack-keybind:`dwarfmonitor` -Widget configuration: - -The following types of widgets (defined in :file:`hack/lua/plugins/dwarfmonitor.lua`) -can be displayed on the main fortress mode screen: - -:date: Show the in-game date -:misery: Show overall happiness levels of all dwarves -:weather: Show current weather (rain/snow) -:cursor: Show the current mouse cursor position +:index:`Measure fort happiness and efficiency. +` Also show heads-up +display widgets with live fort statistics. + +Usage: + +``dwarfmonitor enable `` + Start tracking a specific facet of fortress life. The ``mode`` can be + "work", "misery", "date", "weather", or "all". This will show the + corresponding on-screen widgets, if applicable. +``dwarfmonitor disable `` + Stop monitoring ``mode`` and disable corresponding widgets. +``dwarfmonitor stats`` + Show statistics summary. +``dwarfmonitor prefs`` + Show summary of dwarf preferences. +``dwarfmonitor reload`` + Reload the widget configuration file (``dfhack-config/dwarfmonitor.json``). + +Widget configuration +-------------------- + +The following types of widgets (defined in +:file:`hack/lua/plugins/dwarfmonitor.lua`) can be displayed on the main fortress +mode screen: + +``misery`` + Show overall happiness levels of all dwarves. +``date`` + Show the in-game date. +``weather`` + Show current weather (e.g. rain/snow). +``cursor`` + Show the current mouse cursor position. The file :file:`dfhack-config/dwarfmonitor.json` can be edited to control the -positions and settings of all widgets displayed. This file should contain a -JSON object with the key ``widgets`` containing an array of objects - see the -included file in the ``dfhack-config`` folder for an example: +positions and settings of all widgets. This file should contain a JSON object +with the key ``widgets`` containing an array of objects: -.. code-block:: lua +.. code-block:: json { "widgets": [ @@ -45,7 +58,7 @@ included file in the ``dfhack-config`` folder for an example: X and Y coordinates begin at zero (in the upper left corner of the screen). Negative coordinates will be treated as distances from the lower right corner, beginning at 1 - e.g. an x coordinate of 0 is the leftmost column, while an x -coordinate of 1 is the rightmost column. +coordinate of -1 is the rightmost column. By default, the x and y coordinates given correspond to the leftmost tile of the widget. Including an ``anchor`` option set to ``right`` will cause the diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 84e9b9abe..493796fc6 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -2075,20 +2075,9 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector \n" - " Start monitoring \n" - " can be \"work\", \"misery\", \"weather\", or \"all\"\n" - "dwarfmonitor disable \n" - " as above\n\n" - "dwarfmonitor stats\n" - " Show statistics summary\n" - "dwarfmonitor prefs\n" - " Show dwarf preferences summary\n\n" - "dwarfmonitor reload\n" - " Reload configuration file (dfhack-config/dwarfmonitor.json)\n" - )); + "dwarfmonitor", + "Records dwarf activity to measure fort efficiency", + dwarfmonitor_cmd)); dm_lua::state=Lua::Core::State; if (dm_lua::state == NULL) From 91e3f6767c303267a43ebec02c361f8df53ab359 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 24 Jul 2022 23:10:25 -0700 Subject: [PATCH 315/854] update dwarfmonitor short description --- plugins/dwarfmonitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 493796fc6..1a453e0d5 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -2076,7 +2076,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Sun, 24 Jul 2022 23:24:35 -0700 Subject: [PATCH 316/854] code block is not valid json --- docs/plugins/dwarfmonitor.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/dwarfmonitor.rst b/docs/plugins/dwarfmonitor.rst index 295377c2d..6c1472c7d 100644 --- a/docs/plugins/dwarfmonitor.rst +++ b/docs/plugins/dwarfmonitor.rst @@ -42,7 +42,7 @@ The file :file:`dfhack-config/dwarfmonitor.json` can be edited to control the positions and settings of all widgets. This file should contain a JSON object with the key ``widgets`` containing an array of objects: -.. code-block:: json +.. code-block:: lua { "widgets": [ From 32e2ac21031fb3d7769c5b53b30f99505e70f0e5 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 24 Jul 2022 23:24:52 -0700 Subject: [PATCH 317/854] update docs for dwarfvet --- docs/plugins/dwarfvet.rst | 30 +++++++++++++++++------------- plugins/dwarfvet.cpp | 10 ++-------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/docs/plugins/dwarfvet.rst b/docs/plugins/dwarfvet.rst index 1ceb1bc2f..6a1c5a60f 100644 --- a/docs/plugins/dwarfvet.rst +++ b/docs/plugins/dwarfvet.rst @@ -1,19 +1,23 @@ dwarfvet ======== -Enables Animal Caretaker functionality +Tags: +:dfhack-keybind:`dwarfvet` -Always annoyed your dragons become useless after a minor injury? Well, with -dwarfvet, your animals become first rate members of your fort. It can also -be used to train medical skills. +:index:`Allows animals to be treated at animal hospitals. +` -Animals need to be treated in an animal hospital, which is simply a hospital -that is also an animal training zone. The console will print out a list on game -load, and whenever one is added or removed. Dwarfs must have the Animal Caretaker -labor to treat animals. Normal medical skills are used (and no experience is given -to the Animal Caretaker skill). +Annoyed your dragons become useless after a minor injury? Well, with dwarfvet, +injured animals will be treated at an animal hospital, which is simply a hospital +that is also an animal training zone. Dwarfs with the Animal Caretaker labor +enabled will come to the hospital to treat animals. Normal medical skills are +used (and trained), but no experience is given to the Animal Caretaker skill +itself. -Options: +Usage: -:enable: Enables Animal Caretakers to treat and manage animals -:disable: Turns off the plguin -:report: Reports all zones that the game considers animal hospitals +``enable dwarfvet`` + Enables the plugin. +``dwarfvet report`` + Reports all zones that the game considers animal hospitals. +``dwarfvet report-usage`` + Reports on animals currently being treated. diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index 30308bc57..356fda238 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -370,14 +370,8 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Sun, 24 Jul 2022 23:39:13 -0700 Subject: [PATCH 318/854] update docs for embark-assistant --- docs/plugins/embark-assistant.rst | 25 ++++++++++++++----- plugins/embark-assistant/embark-assistant.cpp | 14 +++-------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/docs/plugins/embark-assistant.rst b/docs/plugins/embark-assistant.rst index 4500a919e..88f59efab 100644 --- a/docs/plugins/embark-assistant.rst +++ b/docs/plugins/embark-assistant.rst @@ -1,9 +1,22 @@ embark-assistant ================ +Tags: +:dfhack-keybind:`embark-assistant` -This plugin provides embark site selection help. It has to be run with the -``embark-assistant`` command while the pre-embark screen is displayed and shows -extended (and correct(?)) resource information for the embark rectangle as well -as normally undisplayed sites in the current embark region. It also has a site -selection tool with more options than DF's vanilla search tool. For detailed -help invoke the in game info screen. +:index:`Embark site selection support. +` Run this command while the +pre-embark screen is displayed to show extended (and correct(?)) resource +information for the embark rectangle as well as normally undisplayed sites in +the current embark region. You will also have access to a site selection tool +with far more options than DF's vanilla search tool. + +If you enable the plugin, you'll also be able to invoke ``embark-assistant`` +with the :kdb:`A` key on the pre-embark screen. + +Usage:: + + enable embark-assistant + embark-assistant + +Note the site selection tool requires a display height of at least 46 lines to +display properly. diff --git a/plugins/embark-assistant/embark-assistant.cpp b/plugins/embark-assistant/embark-assistant.cpp index 61a1b4f83..b7afe494b 100644 --- a/plugins/embark-assistant/embark-assistant.cpp +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -178,17 +178,9 @@ IMPLEMENT_VMETHOD_INTERPOSE(start_site_hook, feed); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "embark-assistant", "Embark site selection support.", - embark_assistant, false, /* false means that the command can be used from non-interactive user interface */ - // Extended help string. Used by CR_WRONG_USAGE and the help command: - " This command starts the embark-assist plugin that provides embark site\n" - " selection help. It has to be called while the pre-embark screen is\n" - " displayed and shows extended (and correct(?)) resource information for\n" - " the embark rectangle as well as normally undisplayed sites in the\n" - " current embark region. It also has a site selection tool with more\n" - " options than DF's vanilla search tool. For detailed help invoke the\n" - " in game info screen. Prefers 46 lines to display properly.\n" - )); + "embark-assistant", + "Embark site selection support.", + embark_assistant)); return CR_OK; } From 02cc085a437e07e7b1e5ea379657879baa9592d6 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 24 Jul 2022 23:59:04 -0700 Subject: [PATCH 319/854] update docs for embark-tools --- docs/plugins/embark-assistant.rst | 2 +- docs/plugins/embark-tools.rst | 36 ++++++++++++++++++++++++------- plugins/embark-tools.cpp | 14 ++---------- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/docs/plugins/embark-assistant.rst b/docs/plugins/embark-assistant.rst index 88f59efab..3a6081655 100644 --- a/docs/plugins/embark-assistant.rst +++ b/docs/plugins/embark-assistant.rst @@ -11,7 +11,7 @@ the current embark region. You will also have access to a site selection tool with far more options than DF's vanilla search tool. If you enable the plugin, you'll also be able to invoke ``embark-assistant`` -with the :kdb:`A` key on the pre-embark screen. +with the :kbd:`A` key on the pre-embark screen. Usage:: diff --git a/docs/plugins/embark-tools.rst b/docs/plugins/embark-tools.rst index fcab18fc7..987b76df6 100644 --- a/docs/plugins/embark-tools.rst +++ b/docs/plugins/embark-tools.rst @@ -1,12 +1,32 @@ embark-tools ============ -A collection of embark-related tools. Usage and available tools:: +Tags: +:dfhack-keybind:`embark-tools` - embark-tools enable/disable tool [tool]... +:index:`Extend the embark screen functionality. +` -:anywhere: Allows embarking anywhere (including sites, mountain-only biomes, - and oceans). Use with caution. -:mouse: Implements mouse controls (currently in the local embark region only) -:sand: Displays an indicator when sand is present in the currently-selected - area, similar to the default clay/stone indicators. -:sticky: Maintains the selected local area while navigating the world map +Usage:: + + enable embark-tools + embark-tools enable|disable all + embark-tools enable|disable [ ...] + +Avaliable tools are: + +``anywhere`` + Allows embarking anywhere (including sites, mountain-only biomes, and + oceans). Use with caution. +``mouse`` + Implements mouse controls (currently in the local embark region only). +``sand`` + Displays an indicator when sand is present in the currently-selected area, + similar to the default clay/stone indicators. +``sticky`` + Maintains the selected local area while navigating the world map. + +Example +------- + +``embark-tools enable all`` + Enable all embark screen extensions. diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index b87295020..924def798 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -752,20 +752,10 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector second->getId() + ": " + iter->second->getDesc() + "\n"); - } commands.push_back(PluginCommand( "embark-tools", - "A collection of embark tools", - embark_tools_cmd, - false, - help.c_str() - )); + "Extend the embark screen functionality.", + embark_tools_cmd)); return CR_OK; } From 98ad22ddddc69ce06353414270237a62f240fb5b Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 06:08:59 -0700 Subject: [PATCH 320/854] align remaining plugin docs to plugin names --- ...essreader.rst => RemoteFortressReader.rst} | 0 docs/plugins/filltraffic.rst | 12 +++++ docs/plugins/fix-job-postings.rst | 5 -- docs/plugins/job-duplicate.rst | 6 --- docs/plugins/job-material.rst | 15 ------ docs/plugins/job.rst | 15 ------ docs/plugins/jobutils.rst | 40 +++++++++++++++ docs/plugins/liquids-here.rst | 6 --- docs/plugins/liquids.rst | 9 ++++ docs/plugins/nopause.rst | 4 -- docs/plugins/{plant.rst => plants.rst} | 2 + docs/plugins/prospect.rst | 51 ------------------- docs/plugins/prospector.rst | 2 + docs/plugins/rb.rst | 4 -- docs/plugins/restrictice.rst | 4 -- docs/plugins/restrictliquids.rst | 4 -- docs/plugins/reveal.rst | 7 +++ docs/plugins/revflood.rst | 23 --------- docs/plugins/revforget.rst | 23 --------- docs/plugins/revtoggle.rst | 23 --------- docs/plugins/ruby.rst | 2 + docs/plugins/sort-items.rst | 17 ------- docs/plugins/{sort-units.rst => sort.rst} | 16 ++++++ docs/plugins/tiletypes-command.rst | 10 ---- docs/plugins/tiletypes-here-point.rst | 6 --- docs/plugins/tiletypes-here.rst | 14 ----- docs/plugins/tiletypes.rst | 36 +++++++++++++ docs/plugins/unreveal.rst | 23 --------- docs/plugins/workflow.rst | 6 +++ 29 files changed, 132 insertions(+), 253 deletions(-) rename docs/plugins/{remotefortressreader.rst => RemoteFortressReader.rst} (100%) delete mode 100644 docs/plugins/fix-job-postings.rst delete mode 100644 docs/plugins/job-duplicate.rst delete mode 100644 docs/plugins/job-material.rst delete mode 100644 docs/plugins/job.rst create mode 100644 docs/plugins/jobutils.rst delete mode 100644 docs/plugins/liquids-here.rst delete mode 100644 docs/plugins/nopause.rst rename docs/plugins/{plant.rst => plants.rst} (97%) delete mode 100644 docs/plugins/prospect.rst delete mode 100644 docs/plugins/rb.rst delete mode 100644 docs/plugins/restrictice.rst delete mode 100644 docs/plugins/restrictliquids.rst delete mode 100644 docs/plugins/revflood.rst delete mode 100644 docs/plugins/revforget.rst delete mode 100644 docs/plugins/revtoggle.rst delete mode 100644 docs/plugins/sort-items.rst rename docs/plugins/{sort-units.rst => sort.rst} (52%) delete mode 100644 docs/plugins/tiletypes-command.rst delete mode 100644 docs/plugins/tiletypes-here-point.rst delete mode 100644 docs/plugins/tiletypes-here.rst delete mode 100644 docs/plugins/unreveal.rst diff --git a/docs/plugins/remotefortressreader.rst b/docs/plugins/RemoteFortressReader.rst similarity index 100% rename from docs/plugins/remotefortressreader.rst rename to docs/plugins/RemoteFortressReader.rst diff --git a/docs/plugins/filltraffic.rst b/docs/plugins/filltraffic.rst index 93306e9eb..d3e952204 100644 --- a/docs/plugins/filltraffic.rst +++ b/docs/plugins/filltraffic.rst @@ -1,4 +1,6 @@ .. _alltraffic: +.. _restrictice: +.. _restrictliquids: filltraffic =========== @@ -29,3 +31,13 @@ Options: :N: Normal Traffic :L: Low Traffic :R: Restricted Traffic + +restrictice +=========== +Restrict traffic on all tiles on top of visible ice. +See also `alltraffic`, `filltraffic`, and `restrictliquids`. + +restrictliquids +=============== +Restrict traffic on all visible tiles with liquid. +See also `alltraffic`, `filltraffic`, and `restrictice`. diff --git a/docs/plugins/fix-job-postings.rst b/docs/plugins/fix-job-postings.rst deleted file mode 100644 index 198dcd61d..000000000 --- a/docs/plugins/fix-job-postings.rst +++ /dev/null @@ -1,5 +0,0 @@ -fix-job-postings ----------------- -This command fixes crashes caused by previous versions of workflow, mostly in -DFHack 0.40.24-r4, and should be run automatically when loading a world (but can -also be run manually if desired). diff --git a/docs/plugins/job-duplicate.rst b/docs/plugins/job-duplicate.rst deleted file mode 100644 index a18d73df3..000000000 --- a/docs/plugins/job-duplicate.rst +++ /dev/null @@ -1,6 +0,0 @@ -job-duplicate -============= -In :kbd:`q` mode, when a job is highlighted within a workshop or furnace -building, calling ``job-duplicate`` instantly duplicates the job. - -:dfhack-keybind:`job-duplicate` diff --git a/docs/plugins/job-material.rst b/docs/plugins/job-material.rst deleted file mode 100644 index f1b37fc1d..000000000 --- a/docs/plugins/job-material.rst +++ /dev/null @@ -1,15 +0,0 @@ -job-material -============ -Alter the material of the selected job. Similar to ``job item-material ...`` - -Invoked as:: - - job-material - -:dfhack-keybind:`job-material` - -* In :kbd:`q` mode, when a job is highlighted within a workshop or furnace, - changes the material of the job. Only inorganic materials can be used - in this mode. -* In :kbd:`b` mode, during selection of building components positions the cursor - over the first available choice with the matching material. diff --git a/docs/plugins/job.rst b/docs/plugins/job.rst deleted file mode 100644 index 26f163864..000000000 --- a/docs/plugins/job.rst +++ /dev/null @@ -1,15 +0,0 @@ -job -=== -Command for general job query and manipulation. - -Options: - -*no extra options* - Print details of the current job. The job can be selected - in a workshop, or the unit/jobs screen. -**list** - Print details of all jobs in the selected workshop. -**item-material ** - Replace the exact material id in the job item. -**item-type ** - Replace the exact item type id in the job item. diff --git a/docs/plugins/jobutils.rst b/docs/plugins/jobutils.rst new file mode 100644 index 000000000..230c69f4c --- /dev/null +++ b/docs/plugins/jobutils.rst @@ -0,0 +1,40 @@ +.. _job: + +job +=== +Command for general job query and manipulation. + +Options: + +*no extra options* + Print details of the current job. The job can be selected + in a workshop, or the unit/jobs screen. +**list** + Print details of all jobs in the selected workshop. +**item-material ** + Replace the exact material id in the job item. +**item-type ** + Replace the exact item type id in the job item. + +job-duplicate +============= +In :kbd:`q` mode, when a job is highlighted within a workshop or furnace +building, calling ``job-duplicate`` instantly duplicates the job. + +:dfhack-keybind:`job-duplicate` + +job-material +============ +Alter the material of the selected job. Similar to ``job item-material ...`` + +Invoked as:: + + job-material + +:dfhack-keybind:`job-material` + +* In :kbd:`q` mode, when a job is highlighted within a workshop or furnace, + changes the material of the job. Only inorganic materials can be used + in this mode. +* In :kbd:`b` mode, during selection of building components positions the cursor + over the first available choice with the matching material. diff --git a/docs/plugins/liquids-here.rst b/docs/plugins/liquids-here.rst deleted file mode 100644 index d838170a2..000000000 --- a/docs/plugins/liquids-here.rst +++ /dev/null @@ -1,6 +0,0 @@ -liquids-here ------------- -Run the liquid spawner with the current/last settings made in liquids (if no -settings in liquids were made it paints a point of 7/7 magma by default). - -Intended to be used as keybinding. Requires an active in-game cursor. diff --git a/docs/plugins/liquids.rst b/docs/plugins/liquids.rst index d55962ad5..75e41da5b 100644 --- a/docs/plugins/liquids.rst +++ b/docs/plugins/liquids.rst @@ -1,3 +1,5 @@ +.. _liquids-here: + liquids ======= Allows adding magma, water and obsidian to the game. It replaces the normal @@ -63,3 +65,10 @@ Brush size and shape: :block: DF map block with cursor in it (regular spaced 16x16x1 blocks) :column: Column from cursor, up through free space :flood: Flood-fill water tiles from cursor (only makes sense with wclean) + +liquids-here +------------ +Run the liquid spawner with the current/last settings made in liquids (if no +settings in liquids were made it paints a point of 7/7 magma by default). + +Intended to be used as keybinding. Requires an active in-game cursor. diff --git a/docs/plugins/nopause.rst b/docs/plugins/nopause.rst deleted file mode 100644 index c30543187..000000000 --- a/docs/plugins/nopause.rst +++ /dev/null @@ -1,4 +0,0 @@ -nopause -======= -Disables pausing (both manual and automatic) with the exception of pause forced -by `reveal` ``hell``. This is nice for digging under rivers. diff --git a/docs/plugins/plant.rst b/docs/plugins/plants.rst similarity index 97% rename from docs/plugins/plant.rst rename to docs/plugins/plants.rst index b621b890d..0111b0770 100644 --- a/docs/plugins/plant.rst +++ b/docs/plugins/plants.rst @@ -1,3 +1,5 @@ +.. _plant: + plant ===== A tool for creating shrubs, growing, or getting rid of them. diff --git a/docs/plugins/prospect.rst b/docs/plugins/prospect.rst deleted file mode 100644 index a8979f1cc..000000000 --- a/docs/plugins/prospect.rst +++ /dev/null @@ -1,51 +0,0 @@ -prospect -======== - -**Usage:** - - ``prospect [all|hell] []`` - -Shows a summary of resources that exist on the map. By default, only the visible -part of the map is scanned. Include the ``all`` keyword if you want ``prospect`` -to scan the whole map as if it were revealed. Use ``hell`` instead of ``all`` if -you also want to see the Z range of HFS tubes in the 'features' report section. - -**Options:** - -:``-h``, ``--help``: - Shows this help text. -:``-s``, ``--show ``: - Shows only the named comma-separated list of report sections. Report section - names are: summary, liquids, layers, features, ores, gems, veins, shrubs, - and trees. If run during pre-embark, only the layers, ores, gems, and veins - report sections are available. -:``-v``, ``--values``: - Includes material value in the output. Most useful for the 'gems' report - section. - -**Examples:** - -``prospect all`` - Shows the entire report for the entire map. - -``prospect hell --show layers,ores,veins`` - Shows only the layers, ores, and other vein stone report sections, and - includes information on HFS tubes when a fort is loaded. - -``prospect all -sores`` - Show only information about ores for the pre-embark or fortress map report. - -**Pre-embark estimate:** - -If prospect is called during the embark selection screen, it displays an -estimate of layer stone availability. If the ``all`` keyword is specified, it -also estimates ores, gems, and vein material. The estimate covers all tiles of -the embark rectangle. - -.. note:: - - The results of pre-embark prospect are an *estimate*, and can at best be - expected to be somewhere within +/- 30% of the true amount; sometimes it - does a lot worse. Especially, it is not clear how to precisely compute how - many soil layers there will be in a given embark tile, so it can report a - whole extra layer, or omit one that is actually present. diff --git a/docs/plugins/prospector.rst b/docs/plugins/prospector.rst index a8979f1cc..03dd193f8 100644 --- a/docs/plugins/prospector.rst +++ b/docs/plugins/prospector.rst @@ -1,3 +1,5 @@ +.. _prospect: + prospect ======== diff --git a/docs/plugins/rb.rst b/docs/plugins/rb.rst deleted file mode 100644 index 359d6149b..000000000 --- a/docs/plugins/rb.rst +++ /dev/null @@ -1,4 +0,0 @@ -ruby -==== -Ruby language plugin, which evaluates the following arguments as a ruby string. -Best used as ``:rb [string]``, for the special parsing mode. Alias ``rb_eval``. diff --git a/docs/plugins/restrictice.rst b/docs/plugins/restrictice.rst deleted file mode 100644 index 558fb5679..000000000 --- a/docs/plugins/restrictice.rst +++ /dev/null @@ -1,4 +0,0 @@ -restrictice -=========== -Restrict traffic on all tiles on top of visible ice. -See also `alltraffic`, `filltraffic`, and `restrictliquids`. diff --git a/docs/plugins/restrictliquids.rst b/docs/plugins/restrictliquids.rst deleted file mode 100644 index 4f6cd9550..000000000 --- a/docs/plugins/restrictliquids.rst +++ /dev/null @@ -1,4 +0,0 @@ -restrictliquids -=============== -Restrict traffic on all visible tiles with liquid. -See also `alltraffic`, `filltraffic`, and `restrictice`. diff --git a/docs/plugins/reveal.rst b/docs/plugins/reveal.rst index da7be8277..42f490aa4 100644 --- a/docs/plugins/reveal.rst +++ b/docs/plugins/reveal.rst @@ -1,3 +1,5 @@ +.. _revflood: + reveal ====== This reveals the map. By default, HFS will remain hidden so that the demons @@ -21,3 +23,8 @@ Usage and related commands: :revforget: Discard info about what was visible before revealing the map. Only useful where (e.g.) you abandoned with the fort revealed and no longer want the data. + +nopause +======= +Disables pausing (both manual and automatic) with the exception of pause forced +by `reveal` ``hell``. This is nice for digging under rivers. diff --git a/docs/plugins/revflood.rst b/docs/plugins/revflood.rst deleted file mode 100644 index da7be8277..000000000 --- a/docs/plugins/revflood.rst +++ /dev/null @@ -1,23 +0,0 @@ -reveal -====== -This reveals the map. By default, HFS will remain hidden so that the demons -don't spawn. You can use ``reveal hell`` to reveal everything. With hell revealed, -you won't be able to unpause until you hide the map again. If you really want -to unpause with hell revealed, use ``reveal demons``. - -Reveal also works in adventure mode, but any of its effects are negated once -you move. When you use it this way, you don't need to run ``unreveal``. - -Usage and related commands: - -:reveal: Reveal the whole map, except for HFS to avoid demons spawning -:reveal hell: Also show hell, but requires ``unreveal`` before unpausing -:reveal demon: Reveals everything and allows unpausing - good luck! -:unreveal: Reverts the effects of ``reveal`` -:revtoggle: Switches between ``reveal`` and ``unreveal`` -:revflood: Hide everything, then reveal tiles with a path to the cursor. - Note that tiles behind constructed walls are also revealed as a - workaround for :bug:`1871`. -:revforget: Discard info about what was visible before revealing the map. - Only useful where (e.g.) you abandoned with the fort revealed - and no longer want the data. diff --git a/docs/plugins/revforget.rst b/docs/plugins/revforget.rst deleted file mode 100644 index da7be8277..000000000 --- a/docs/plugins/revforget.rst +++ /dev/null @@ -1,23 +0,0 @@ -reveal -====== -This reveals the map. By default, HFS will remain hidden so that the demons -don't spawn. You can use ``reveal hell`` to reveal everything. With hell revealed, -you won't be able to unpause until you hide the map again. If you really want -to unpause with hell revealed, use ``reveal demons``. - -Reveal also works in adventure mode, but any of its effects are negated once -you move. When you use it this way, you don't need to run ``unreveal``. - -Usage and related commands: - -:reveal: Reveal the whole map, except for HFS to avoid demons spawning -:reveal hell: Also show hell, but requires ``unreveal`` before unpausing -:reveal demon: Reveals everything and allows unpausing - good luck! -:unreveal: Reverts the effects of ``reveal`` -:revtoggle: Switches between ``reveal`` and ``unreveal`` -:revflood: Hide everything, then reveal tiles with a path to the cursor. - Note that tiles behind constructed walls are also revealed as a - workaround for :bug:`1871`. -:revforget: Discard info about what was visible before revealing the map. - Only useful where (e.g.) you abandoned with the fort revealed - and no longer want the data. diff --git a/docs/plugins/revtoggle.rst b/docs/plugins/revtoggle.rst deleted file mode 100644 index da7be8277..000000000 --- a/docs/plugins/revtoggle.rst +++ /dev/null @@ -1,23 +0,0 @@ -reveal -====== -This reveals the map. By default, HFS will remain hidden so that the demons -don't spawn. You can use ``reveal hell`` to reveal everything. With hell revealed, -you won't be able to unpause until you hide the map again. If you really want -to unpause with hell revealed, use ``reveal demons``. - -Reveal also works in adventure mode, but any of its effects are negated once -you move. When you use it this way, you don't need to run ``unreveal``. - -Usage and related commands: - -:reveal: Reveal the whole map, except for HFS to avoid demons spawning -:reveal hell: Also show hell, but requires ``unreveal`` before unpausing -:reveal demon: Reveals everything and allows unpausing - good luck! -:unreveal: Reverts the effects of ``reveal`` -:revtoggle: Switches between ``reveal`` and ``unreveal`` -:revflood: Hide everything, then reveal tiles with a path to the cursor. - Note that tiles behind constructed walls are also revealed as a - workaround for :bug:`1871`. -:revforget: Discard info about what was visible before revealing the map. - Only useful where (e.g.) you abandoned with the fort revealed - and no longer want the data. diff --git a/docs/plugins/ruby.rst b/docs/plugins/ruby.rst index 359d6149b..bdd5521a4 100644 --- a/docs/plugins/ruby.rst +++ b/docs/plugins/ruby.rst @@ -1,3 +1,5 @@ +.. _rb: + ruby ==== Ruby language plugin, which evaluates the following arguments as a ruby string. diff --git a/docs/plugins/sort-items.rst b/docs/plugins/sort-items.rst deleted file mode 100644 index d18f33c4e..000000000 --- a/docs/plugins/sort-items.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. _sort: - -sort-items -========== -Sort the visible item list:: - - sort-items order [order...] - -Sort the item list using the given sequence of comparisons. -The ``<`` prefix for an order makes undefined values sort first. -The ``>`` prefix reverses the sort order for defined values. - -Item order examples:: - - description material wear type quality - -The orderings are defined in ``hack/lua/plugins/sort/*.lua`` diff --git a/docs/plugins/sort-units.rst b/docs/plugins/sort.rst similarity index 52% rename from docs/plugins/sort-units.rst rename to docs/plugins/sort.rst index 1a88381ce..cc86b3ddd 100644 --- a/docs/plugins/sort-units.rst +++ b/docs/plugins/sort.rst @@ -1,3 +1,19 @@ +sort-items +========== +Sort the visible item list:: + + sort-items order [order...] + +Sort the item list using the given sequence of comparisons. +The ``<`` prefix for an order makes undefined values sort first. +The ``>`` prefix reverses the sort order for defined values. + +Item order examples:: + + description material wear type quality + +The orderings are defined in ``hack/lua/plugins/sort/*.lua`` + sort-units ========== Sort the visible unit list:: diff --git a/docs/plugins/tiletypes-command.rst b/docs/plugins/tiletypes-command.rst deleted file mode 100644 index 0f75c633e..000000000 --- a/docs/plugins/tiletypes-command.rst +++ /dev/null @@ -1,10 +0,0 @@ -tiletypes-command ------------------ -Runs tiletypes commands, separated by ``;``. This makes it possible to change -tiletypes modes from a hotkey or via dfhack-run. - -Example:: - - tiletypes-command p any ; p s wall ; p sp normal - -This resets the paint filter to unsmoothed walls. diff --git a/docs/plugins/tiletypes-here-point.rst b/docs/plugins/tiletypes-here-point.rst deleted file mode 100644 index 8951bdff8..000000000 --- a/docs/plugins/tiletypes-here-point.rst +++ /dev/null @@ -1,6 +0,0 @@ -tiletypes-here-point --------------------- -Apply the current tiletypes options at the in-game cursor position to a single -tile. Can be used from a hotkey. - -This command supports the same options as `tiletypes-here` above. diff --git a/docs/plugins/tiletypes-here.rst b/docs/plugins/tiletypes-here.rst deleted file mode 100644 index fe9ceefd9..000000000 --- a/docs/plugins/tiletypes-here.rst +++ /dev/null @@ -1,14 +0,0 @@ -tiletypes-here --------------- -Apply the current tiletypes options at the in-game cursor position, including -the brush. Can be used from a hotkey. - -Options: - -:``-c``, ``--cursor ,,``: - Use the specified map coordinates instead of the current cursor position. If - this option is specified, then an active game map cursor is not necessary. -:``-h``, ``--help``: - Show command help text. -:``-q``, ``--quiet``: - Suppress non-error status output. diff --git a/docs/plugins/tiletypes.rst b/docs/plugins/tiletypes.rst index 41f780521..74af00240 100644 --- a/docs/plugins/tiletypes.rst +++ b/docs/plugins/tiletypes.rst @@ -1,3 +1,6 @@ +.. _tiletypes-here: +.. _tiletypes-here-point: + tiletypes ========= Can be used for painting map tiles and is an interactive command, much like @@ -80,3 +83,36 @@ The range starts at the position of the cursor and goes to the east, south and up. For more details, use ``tiletypes help``. + +tiletypes-command +----------------- +Runs tiletypes commands, separated by ``;``. This makes it possible to change +tiletypes modes from a hotkey or via dfhack-run. + +Example:: + + tiletypes-command p any ; p s wall ; p sp normal + +This resets the paint filter to unsmoothed walls. + +tiletypes-here-point +-------------------- +Apply the current tiletypes options at the in-game cursor position to a single +tile. Can be used from a hotkey. + +This command supports the same options as `tiletypes-here` above. + +tiletypes-here +-------------- +Apply the current tiletypes options at the in-game cursor position, including +the brush. Can be used from a hotkey. + +Options: + +:``-c``, ``--cursor ,,``: + Use the specified map coordinates instead of the current cursor position. If + this option is specified, then an active game map cursor is not necessary. +:``-h``, ``--help``: + Show command help text. +:``-q``, ``--quiet``: + Suppress non-error status output. diff --git a/docs/plugins/unreveal.rst b/docs/plugins/unreveal.rst deleted file mode 100644 index da7be8277..000000000 --- a/docs/plugins/unreveal.rst +++ /dev/null @@ -1,23 +0,0 @@ -reveal -====== -This reveals the map. By default, HFS will remain hidden so that the demons -don't spawn. You can use ``reveal hell`` to reveal everything. With hell revealed, -you won't be able to unpause until you hide the map again. If you really want -to unpause with hell revealed, use ``reveal demons``. - -Reveal also works in adventure mode, but any of its effects are negated once -you move. When you use it this way, you don't need to run ``unreveal``. - -Usage and related commands: - -:reveal: Reveal the whole map, except for HFS to avoid demons spawning -:reveal hell: Also show hell, but requires ``unreveal`` before unpausing -:reveal demon: Reveals everything and allows unpausing - good luck! -:unreveal: Reverts the effects of ``reveal`` -:revtoggle: Switches between ``reveal`` and ``unreveal`` -:revflood: Hide everything, then reveal tiles with a path to the cursor. - Note that tiles behind constructed walls are also revealed as a - workaround for :bug:`1871`. -:revforget: Discard info about what was visible before revealing the map. - Only useful where (e.g.) you abandoned with the fort revealed - and no longer want the data. diff --git a/docs/plugins/workflow.rst b/docs/plugins/workflow.rst index 17db57e69..f6b8c4475 100644 --- a/docs/plugins/workflow.rst +++ b/docs/plugins/workflow.rst @@ -111,3 +111,9 @@ Make sure there are always 80-100 units of dimple dye:: Maintain 10-100 locally-made crafts of exceptional quality:: workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90 + +fix-job-postings +---------------- +This command fixes crashes caused by previous versions of workflow, mostly in +DFHack 0.40.24-r4, and should be run automatically when loading a world (but can +also be run manually if desired). From 4132dbdbbb771189025c39bfb36f899741068112 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 06:51:40 -0700 Subject: [PATCH 321/854] remove docs for no-command, un-enableable plugin it's already documented in lua-api --- docs/plugins/building-hacks.rst | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 docs/plugins/building-hacks.rst diff --git a/docs/plugins/building-hacks.rst b/docs/plugins/building-hacks.rst deleted file mode 100644 index bdae83392..000000000 --- a/docs/plugins/building-hacks.rst +++ /dev/null @@ -1,13 +0,0 @@ -building-hacks -============== -Tags: - -:index:`Allows mods to create and manage powered workshops. -` - -Usage:: - - enable building-hacks - -Please see the `building-hacks-api` for information on accessing this plugin -from Lua scripts. From 6b1c0b5308fcec549ef07dc8269f51f14827ee7e Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 06:53:08 -0700 Subject: [PATCH 322/854] rename building-hacks section --- docs/Lua API.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index aba0bcab7..107a0ba2e 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4287,10 +4287,10 @@ blueprint files: The names of the functions are also available as the keys of the ``valid_phases`` table. -.. _building-hacks-api: +.. _building-hacks: -building-hacks API -================== +building-hacks +============== This plugin overwrites some methods in workshop df class so that mechanical workshops are possible. Although plugin export a function it's recommended to use lua decorated function. From 38c17b5215b5cc241157c227375edd755e21bc17 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 06:56:21 -0700 Subject: [PATCH 323/854] update docs for RemoteFortressReader --- docs/plugins/RemoteFortressReader.rst | 20 ++++++++++++++++--- .../remotefortressreader.cpp | 8 +++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/docs/plugins/RemoteFortressReader.rst b/docs/plugins/RemoteFortressReader.rst index 287debca8..103c904db 100644 --- a/docs/plugins/RemoteFortressReader.rst +++ b/docs/plugins/RemoteFortressReader.rst @@ -1,4 +1,18 @@ -remotefortressreader +RemoteFortressReader ==================== -An in-development plugin for realtime fortress visualisation. -See :forums:`Armok Vision <146473>`. +Tags: +:dfhack-keybind:`RemoteFortressReader_version` +:dfhack-keybind:`load-art-image-chunk` + +:index:`Backend for Armok Vision. +` Provides an API for realtime +remote fortress visualization. See :forums:`Armok Vision <146473>`. + +Usage: + +``enable RemoteFortressReader`` + Enable the plugin. +``RemoteFortressReader_version`` + Print the loaded RemoteFortressReader version. +``load-art-image-chunk `` + Gets an art image chunk by index, loading from disk if necessary. diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 9ab9a7502..643e92b21 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -221,12 +221,14 @@ DFHACK_PLUGIN_IS_ENABLED(enableUpdates); // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("RemoteFortressReader_version", "List the loaded RemoteFortressReader version", RemoteFortressReader_version, false, "This is used for plugin version checking.")); + commands.push_back(PluginCommand( + "RemoteFortressReader_version", + "List the loaded RemoteFortressReader version", + RemoteFortressReader_version)); commands.push_back(PluginCommand( "load-art-image-chunk", "Gets an art image chunk by index, loading from disk if necessary", - loadArtImageChunk, false, - "Usage: load_art_image_chunk N, where N is the id of the chunk to get.")); + loadArtImageChunk)); enableUpdates = true; return CR_OK; } From 886289d805af845ba8d7bc50afd8d804c8d74abc Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 10:21:33 -0700 Subject: [PATCH 324/854] update docs for fix-armory --- docs/plugins/fix-armory.rst | 12 ++++++++++-- plugins/fix-armory.cpp | 9 +++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/plugins/fix-armory.rst b/docs/plugins/fix-armory.rst index f4b988420..dfc5eef7f 100644 --- a/docs/plugins/fix-armory.rst +++ b/docs/plugins/fix-armory.rst @@ -1,4 +1,12 @@ fix-armory ========== -`This plugin requires a binpatch `, which has not -been available since DF 0.34.11 +Tags: +:dfhack-keybind:`fix-armory` + +Allow the military to store equipment in barracks. However, +`this plugin requires a binpatch `, which has not +been available since DF 0.34.11. + +Usage:: + + enable fix-armory diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 0f63b4d93..7dfefff0f 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -70,12 +70,9 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "fix-armory", "Enables or disables the fix-armory plugin.", fix_armory, false, - " fix-armory enable\n" - " Enables the tweaks.\n" - " fix-armory disable\n" - " Disables the tweaks. All equipment will be hauled off to stockpiles.\n" - )); + "fix-armory", + "Enables or disables the fix-armory plugin.", + fix_armory)); if (Core::getInstance().isMapLoaded()) plugin_onstatechange(out, SC_MAP_LOADED); From a6cb79c2371debb0d763715dd63ff14a178ed1f9 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 10:21:59 -0700 Subject: [PATCH 325/854] remove defunct tool fix-armory --- docs/plugins/fix-armory.rst | 12 - plugins/CMakeLists.txt | 1 - plugins/fix-armory.cpp | 840 ------------------------------------ 3 files changed, 853 deletions(-) delete mode 100644 docs/plugins/fix-armory.rst delete mode 100644 plugins/fix-armory.cpp diff --git a/docs/plugins/fix-armory.rst b/docs/plugins/fix-armory.rst deleted file mode 100644 index dfc5eef7f..000000000 --- a/docs/plugins/fix-armory.rst +++ /dev/null @@ -1,12 +0,0 @@ -fix-armory -========== -Tags: -:dfhack-keybind:`fix-armory` - -Allow the military to store equipment in barracks. However, -`this plugin requires a binpatch `, which has not -been available since DF 0.34.11. - -Usage:: - - enable fix-armory diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 283f74fad..3b7ae17f1 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -120,7 +120,6 @@ if(BUILD_SUPPORTED) dfhack_plugin(eventful eventful.cpp LINK_LIBRARIES lua) dfhack_plugin(fastdwarf fastdwarf.cpp) dfhack_plugin(filltraffic filltraffic.cpp) - dfhack_plugin(fix-armory fix-armory.cpp) dfhack_plugin(fix-unit-occupancy fix-unit-occupancy.cpp) dfhack_plugin(fixveins fixveins.cpp) dfhack_plugin(flows flows.cpp) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp deleted file mode 100644 index 7dfefff0f..000000000 --- a/plugins/fix-armory.cpp +++ /dev/null @@ -1,840 +0,0 @@ -// Fixes containers in barracks to actually work as intended. - -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" - -#include "modules/Gui.h" -#include "modules/Screen.h" -#include "modules/Units.h" -#include "modules/Items.h" -#include "modules/Job.h" -#include "modules/World.h" -#include "modules/Maps.h" - -#include "MiscUtils.h" - -#include "DataDefs.h" -#include -#include "df/ui.h" -#include "df/world.h" -#include "df/squad.h" -#include "df/unit.h" -#include "df/squad_position.h" -#include "df/squad_ammo_spec.h" -#include "df/items_other_id.h" -#include "df/item_weaponst.h" -#include "df/item_armorst.h" -#include "df/item_helmst.h" -#include "df/item_pantsst.h" -#include "df/item_shoesst.h" -#include "df/item_glovesst.h" -#include "df/item_shieldst.h" -#include "df/item_flaskst.h" -#include "df/item_backpackst.h" -#include "df/item_quiverst.h" -#include "df/item_ammost.h" -#include "df/building_weaponrackst.h" -#include "df/building_armorstandst.h" -#include "df/building_cabinetst.h" -#include "df/building_boxst.h" -#include "df/building_squad_use.h" -#include "df/job.h" -#include "df/general_ref_building_holderst.h" -#include "df/general_ref_building_destinationst.h" -#include "df/barrack_preference_category.h" - -#include - -using std::vector; -using std::string; -using std::endl; -using namespace DFHack; -using namespace df::enums; - -using df::global::ui; -using df::global::world; -using df::global::gamemode; -using df::global::ui_build_selector; - -using namespace DFHack::Gui; -using Screen::Pen; - -static command_result fix_armory(color_ostream &out, vector & parameters); - -DFHACK_PLUGIN("fix-armory"); - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event); - -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand( - "fix-armory", - "Enables or disables the fix-armory plugin.", - fix_armory)); - - if (Core::getInstance().isMapLoaded()) - plugin_onstatechange(out, SC_MAP_LOADED); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown (color_ostream &out) -{ - return CR_OK; -} - -/* - * PART 1 - Stop restockpiling of items stored in the armory. - * - * For everything other than ammo this is quite straightforward, - * since the uniform switch code already tries to store items - * in barracks containers, and it is thus known what the intention - * is. Moreover these containers know which squad and member they - * belong to. - * - * For ammo there is no such code (in fact, ammo is never removed - * from a quiver, except when it is dropped itself), so I had to - * apply some improvisation. There is one place where BOX containers - * with Squad Equipment set are used as an anchor location for a - * pathfinding check when assigning ammo, so presumably that's - * the correct place. I however wanted to also differentiate - * training ammo, so came up with the following rules: - * - * 1. Combat ammo and ammo without any allowed use can be stored - * in BOXes marked for Squad Equipment, either directly or via - * containing room. No-allowed-use ammo is assumed to be reserved - * for emergency combat use, or something like that; however if - * it is already stored in a training chest, it won't be moved. - * 1a. If assigned to a squad position, that box can be used _only_ - * for ammo assigned to that specific _squad_. Otherwise, if - * multiple squads can use this room, they will store their - * ammo all mixed up. - * 2. Training ammo can be stored in BOXes within archery ranges - * (designated from archery target) that are enabled for Training. - * Train-only ammo in particular can _only_ be stored in such - * boxes. The inspiration for this comes from some broken code - * for weapon racks in Training rooms. - * - * As an additional feature (partially needed due to the constraints - * of working from an external hack), this plugin also blocks instant - * queueing of stockpiling jobs for items blocked on the ground, if - * these items are assigned to any squad. - * - * Since there apparently still are bugs that cause uniform items to be - * momentarily dropped on ground, this delay is set not to the minimally - * necessary 50 ticks, but to 0.5 - 1.0 in-game days, so as to provide a - * grace period during which the items can be instantly picked up again. - */ - -// Completely block the use of stockpiles -#define NO_STOCKPILES - -// Check if the item is assigned to any use controlled by the military tab -static bool is_assigned_item(df::item *item) -{ - if (!ui) - return false; - - auto type = item->getType(); - int idx = binsearch_index(ui->equipment.items_assigned[type], item->id); - if (idx < 0) - return false; - - return true; -} - -// Check if this ammo item is assigned to this squad with one of the specified uses -static bool is_squad_ammo(df::item *item, df::squad *squad, bool combat, bool train) -{ - for (size_t i = 0; i < squad->ammunition.size(); i++) - { - auto spec = squad->ammunition[i]; - bool cs = spec->flags.bits.use_combat; - bool ts = spec->flags.bits.use_training; - - // no-use ammo assumed to fit any category - if (((cs || !ts) && combat) || ((ts || !cs) && train)) - { - if (binsearch_index(spec->assigned, item->id) >= 0) - return true; - } - } - - return false; -} - -// Recursively check room parents to find out if this ammo item is allowed here -static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_id) -{ - auto squads = holder->getSquads(); - - if (squads) - { - for (size_t i = 0; i < squads->size(); i++) - { - auto use = (*squads)[i]; - - // For containers assigned to a squad, only consider that squad - if (squad_id >= 0 && use->squad_id != squad_id) - continue; - - // Squad Equipment -> combat - bool combat = use->mode.bits.squad_eq; - bool train = false; - - if (combat || train) - { - auto squad = df::squad::find(use->squad_id); - - if (squad && is_squad_ammo(item, squad, combat, train)) - return true; - } - } - } - // Ugh, archery targets don't actually have a squad use vector - else if (holder->getType() == building_type::ArcheryTarget) - { - auto &squads = world->squads.all; - - for (size_t si = 0; si < squads.size(); si++) - { - auto squad = squads[si]; - - // For containers assigned to a squad, only consider that squad - if (squad_id >= 0 && squad->id != squad_id) - continue; - - for (size_t j = 0; j < squad->rooms.size(); j++) - { - auto use = squad->rooms[j]; - - if (use->building_id != holder->id) - continue; - - // Squad Equipment -> combat - bool combat = use->mode.bits.squad_eq; - // Archery target with Train -> training - bool train = use->mode.bits.train; - - if (combat || train) - { - if (is_squad_ammo(item, squad, combat, train)) - return true; - } - - break; - } - } - } - - for (size_t i = 0; i < holder->parents.size(); i++) - if (can_store_ammo_rec(item, holder->parents[i], squad_id)) - return true; - - return false; -} - -// Check if the ammo item can be stored in this container -static bool can_store_ammo(df::item *item, df::building *holder) -{ - // Only chests - if (holder->getType() != building_type::Box) - return false; - - // with appropriate flags set - return can_store_ammo_rec(item, holder, holder->getSpecificSquad()); -} - -// Check if the item is assigned to the squad member who owns this armory building -static bool belongs_to_position(df::item *item, df::building *holder) -{ - int sid = holder->getSpecificSquad(); - if (sid < 0) - return false; - - auto squad = df::squad::find(sid); - if (!squad) - return false; - - int position = holder->getSpecificPosition(); - - // Weapon racks belong to the whole squad, i.e. can be used by any position - if (position == -1 && holder->getType() == building_type::Weaponrack) - { - for (size_t i = 0; i < squad->positions.size(); i++) - { - if (binsearch_index(squad->positions[i]->assigned_items, item->id) >= 0) - return true; - } - } - else - { - auto cpos = vector_get(squad->positions, position); - if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0) - return true; - } - - return false; -} - -// Check if the item is appropriately stored in an armory building -static bool is_in_armory(df::item *item) -{ - if (item->flags.bits.in_inventory || item->flags.bits.on_ground) - return false; - - auto holder = Items::getHolderBuilding(item); - if (!holder) - return false; - - // If indeed in a building, check if it is the right one - if (item->getType() == item_type::AMMO) - return can_store_ammo(item, holder); - else - return belongs_to_position(item, holder); -} - -/* - * Hooks used to affect stockpiling code as it runs, and prevent it - * from doing unwanted stuff. - * - * Toady can simply add these checks directly to the stockpiling code; - * we have to abuse some handy item vmethods. - */ - -template struct armory_hook : Item { - typedef Item interpose_base; - - /* - * This vmethod is called by the actual stockpiling code before it - * tries to queue a job, and is normally used to prevent stockpiling - * of uncollected webs. - */ - DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) - { -#ifdef NO_STOCKPILES - /* - * Completely block any items assigned to a squad from being stored - * in stockpiles. The reason is that I still observe haulers running - * around with bins to pick them up for some reason. There could be - * some unaccounted race conditions involved. - */ - if (is_assigned_item(this)) - return false; -#else - // Block stockpiling of items in the armory. - if (is_in_armory(this)) - return false; - - /* - * When an item is removed from inventory due to Pickup Equipment - * process, the unit code directly invokes the stockpiling code - * and thus creates the job even before the item is actually dropped - * on the ground. We don't want this at all, especially due to the - * grace period idea. - * - * With access to source, that code can just be changed to simply - * drop the item on ground, without running stockpiling code. - */ - if (this->flags.bits.in_inventory) - { - auto holder = Items::getHolderUnit(this); - - // When that call happens, the item is still in inventory - if (holder && is_assigned_item(this)) - { - // And its ID is is this vector - if (::binsearch_index(holder->military.uniform_drop, this->id) >= 0) - return false; - } - } -#endif - - // Call the original vmethod - return INTERPOSE_NEXT(isCollected)(); - } - - /* - * This vmethod is used to actually put the item on the ground. - * When it does that, it also adds it to a vector of items to be - * instanly restockpiled by a loop in another part of the code. - * - * We don't want this either, even more than when removing from - * uniform, because this can happen in lots of various situations, - * including deconstructed containers etc, and we want our own - * armory code below to have a chance to look at the item. - * - * The logical place for this code is in the loop that processes - * that vector, but that part is not virtual. - */ - DEFINE_VMETHOD_INTERPOSE(bool, moveToGround, (int16_t x, int16_t y, int16_t z)) - { - // First, let it do its work - bool rv = INTERPOSE_NEXT(moveToGround)(x, y, z); - - // Prevent instant restockpiling of dropped assigned items. - if (is_assigned_item(this)) - { - // The original vmethod adds the item to this vector to force instant check - auto &ovec = world->items.other[items_other_id::ANY_RECENTLY_DROPPED]; - - // If it is indeed there, remove it - if (erase_from_vector(ovec, &df::item::id, this->id)) - { - // and queue it to be checked normally in 0.5-1 in-game days - // (this is a grace period in case the uniform is dropped just - // for a moment due to a momentary glitch) - this->stockpile_countdown = 12 + random_int(12); - this->stockpile_delay = 0; - } - } - - return rv; - } -}; - -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); - -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); - -/* - * PART 2 - Actively queue jobs to haul assigned items to the armory. - * - * The logical place for this of course is in the same code that decides - * to put stuff in stockpiles, alongside the checks to prevent moving - * stuff away from the armory. We just run it independently every 50 - * simulation frames. - */ - -// Check if this item is loose and can be moved to armory -static bool can_store_item(df::item *item) -{ - // bad id, or cooldown timer still counting - if (!item || item->stockpile_countdown > 0) - return false; - - // bad flags? - if (item->flags.bits.in_job || - item->flags.bits.removed || - item->flags.bits.in_building || - item->flags.bits.encased || - item->flags.bits.owned || - item->flags.bits.forbid || - item->flags.bits.on_fire) - return false; - - // in unit inventory? - auto top = item; - - while (top->flags.bits.in_inventory) - { - auto parent = Items::getContainer(top); - if (!parent) break; - top = parent; - } - - if (Items::getGeneralRef(top, general_ref_type::UNIT_HOLDER)) - return false; - - // already in armory? - if (is_in_armory(item)) - return false; - - return true; -} - -// Queue a job to store the item in the building, if possible -static bool try_store_item(df::building *target, df::item *item) -{ - // Check if the dwarves can path between the target and the item - df::coord tpos(target->centerx, target->centery, target->z); - df::coord ipos = Items::getPosition(item); - - if (!Maps::canWalkBetween(tpos, ipos)) - return false; - - // Check if the target has enough space left - if (!target->canStoreItem(item, true)) - return false; - - // Create the job - auto href = df::allocate(); - if (!href) - return false; - - auto job = new df::job(); - - job->pos = tpos; - - bool dest = false; - - // Choose the job type - correct matching is needed so that - // later canStoreItem calls would take the job into account. - switch (target->getType()) { - case building_type::Weaponrack: - job->job_type = job_type::StoreWeapon; - // Without this flag dwarves will pick up the item, and - // then dismiss the job and put it back into the stockpile: - job->flags.bits.specific_dropoff = true; - break; - case building_type::Armorstand: - job->job_type = job_type::StoreArmor; - job->flags.bits.specific_dropoff = true; - break; - case building_type::Cabinet: - job->job_type = job_type::StoreOwnedItem; - dest = true; - break; - default: - job->job_type = job_type::StoreItemInHospital; - dest = true; - break; - } - - // job <-> item link - if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) - { - delete job; - delete href; - return false; - } - - // job <-> building link - href->building_id = target->id; - target->jobs.push_back(job); - job->general_refs.push_back(href); - - // Two of the jobs need this link to find the job in canStoreItem(). - // They also don't actually need BUILDING_HOLDER, but it doesn't hurt. - if (dest) - { - auto rdest = df::allocate(); - - if (rdest) - { - rdest->building_id = target->id; - job->general_refs.push_back(rdest); - } - } - - // add to job list - Job::linkIntoWorld(job); - return true; -} - -// Store the item into the first building in the list that would accept it. -static void try_store_item(std::vector &vec, df::item *item) -{ - for (size_t i = 0; i < vec.size(); i++) - { - auto target = df::building::find(vec[i]); - if (!target) - continue; - - if (try_store_item(target, item)) - return; - } -} - -// Store the items into appropriate armory buildings -static void try_store_item_set(std::vector &items, df::squad *squad, df::squad_position *pos) -{ - for (size_t j = 0; j < items.size(); j++) - { - auto item = df::item::find(items[j]); - - // not loose - if (!can_store_item(item)) - continue; - - // queue jobs to put it in the appropriate container - if (item->isWeapon()) - try_store_item(squad->rack_combat, item); - else if (item->isClothing()) - try_store_item(pos->preferences[barrack_preference_category::Cabinet], item); - else if (item->isArmorNotClothing()) - try_store_item(pos->preferences[barrack_preference_category::Armorstand], item); - else - try_store_item(pos->preferences[barrack_preference_category::Box], item); - } -} - -// For storing ammo, use a data structure sorted by free space, to even out the load -typedef std::map > ammo_box_set; - -// Enumerate boxes in the room, adding them to the set -static void index_boxes(df::building *root, ammo_box_set &group, int squad_id) -{ - if (root->getType() == building_type::Box) - { - int id = root->getSpecificSquad(); - - if (id < 0 || id == squad_id) - { - //color_ostream_proxy out(Core::getInstance().getConsole()); - //out.print("%08x %d\n", unsigned(root), root->getFreeCapacity(true)); - - group[root->getFreeCapacity(true)].insert(root); - } - } - - for (size_t i = 0; i < root->children.size(); i++) - index_boxes(root->children[i], group, squad_id); -} - -// Loop over the set from most empty to least empty -static bool try_store_ammo(df::item *item, ammo_box_set &group) -{ - int volume = item->getVolume(); - - for (auto it = group.rbegin(); it != group.rend(); ++it) - { - if (it->first < volume) - break; - - for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) - { - auto bld = *it2; - - if (try_store_item(bld, item)) - { - it->second.erase(bld); - group[bld->getFreeCapacity(true)].insert(bld); - return true; - } - } - } - - return false; -} - -// Collect chests for ammo storage -static void index_ammo_boxes(df::squad *squad, ammo_box_set &train_set, ammo_box_set &combat_set) -{ - for (size_t j = 0; j < squad->rooms.size(); j++) - { - auto room = squad->rooms[j]; - auto bld = df::building::find(room->building_id); - - // Chests in rooms marked for Squad Equipment used for combat ammo - if (room->mode.bits.squad_eq) - index_boxes(bld, combat_set, squad->id); - - // Chests in archery ranges used for training ammo - if (room->mode.bits.train && bld->getType() == building_type::ArcheryTarget) - index_boxes(bld, train_set, squad->id); - } -} - -// Store ammo into appropriate chests -static void try_store_ammo(df::squad *squad) -{ - bool indexed = false; - ammo_box_set train_set, combat_set; - - for (size_t i = 0; i < squad->ammunition.size(); i++) - { - auto spec = squad->ammunition[i]; - bool cs = spec->flags.bits.use_combat; - bool ts = spec->flags.bits.use_training; - - for (size_t j = 0; j < spec->assigned.size(); j++) - { - auto item = df::item::find(spec->assigned[j]); - - // not loose - if (!can_store_item(item)) - continue; - - // compute the maps lazily - if (!indexed) - { - indexed = true; - index_ammo_boxes(squad, train_set, combat_set); - } - - // BUG: if the same container is in both sets, - // when a job is queued, the free space in the other - // set will not be updated, which could lead to uneven - // loading - but not to overflowing the container! - - // As said above, combat goes into Squad Equipment - if (cs && try_store_ammo(item, combat_set)) - continue; - // Training goes into Archery Range with Train - if (ts && try_store_ammo(item, train_set)) - continue; - // No use goes into combat - if (!(ts || cs) && try_store_ammo(item, combat_set)) - continue; - } - } -} - -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - -DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event) -{ - if (!is_enabled) - return CR_OK; - - // Process every 50th frame, sort of like regular stockpiling does - if (DF_GLOBAL_VALUE(cur_year_tick,1) % 50 != 0) - return CR_OK; - - // Loop over squads - auto &squads = world->squads.all; - - for (size_t si = 0; si < squads.size(); si++) - { - auto squad = squads[si]; - - for (size_t i = 0; i < squad->positions.size(); i++) - { - auto pos = squad->positions[i]; - - try_store_item_set(pos->assigned_items, squad, pos); - } - - try_store_ammo(squad); - } - - return CR_OK; -} - -static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, bool enable) -{ - if (!hook.apply(enable)) - out.printerr("Could not %s hook.\n", enable?"activate":"deactivate"); -} - -static void enable_hooks(color_ostream &out, bool enable) -{ - is_enabled = enable; - - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); -} - -static void enable_plugin(color_ostream &out) -{ - auto entry = World::GetPersistentData("fix-armory/enabled", NULL); - if (!entry.isValid()) - { - out.printerr("Could not save the status.\n"); - return; - } - - enable_hooks(out, true); -} - -static void disable_plugin(color_ostream &out) -{ - auto entry = World::GetPersistentData("fix-armory/enabled"); - World::DeletePersistentData(entry); - - enable_hooks(out, false); -} - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) { - case SC_MAP_LOADED: - if (!gamemode || *gamemode == game_mode::DWARF) - { - bool enable = World::GetPersistentData("fix-armory/enabled").isValid(); - - if (enable) - { - out.print("Enabling the fix-armory plugin.\n"); - enable_hooks(out, true); - } - else - enable_hooks(out, false); - } - break; - case SC_MAP_UNLOADED: - enable_hooks(out, false); - default: - break; - } - - return CR_OK; -} - -DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) -{ - if (!Core::getInstance().isWorldLoaded()) { - out.printerr("World is not loaded: please load a game first.\n"); - return CR_FAILURE; - } - - if (enable) - enable_plugin(out); - else - disable_plugin(out); - - return CR_OK; -} - -static command_result fix_armory(color_ostream &out, vector ¶meters) -{ - CoreSuspender suspend; - - if (parameters.empty()) - return CR_WRONG_USAGE; - - string cmd = parameters[0]; - - if (cmd == "enable") - return plugin_enable(out, true); - else if (cmd == "disable") - return plugin_enable(out, false); - else - return CR_WRONG_USAGE; - - return CR_OK; -} From 989befa5824bd1ef54fecb5589c933e1409fc994 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 10:22:23 -0700 Subject: [PATCH 326/854] update docs for fastdwarf --- docs/plugins/fastdwarf.rst | 46 ++++++++++++++++++++++++++++---------- plugins/fastdwarf.cpp | 18 ++++----------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/docs/plugins/fastdwarf.rst b/docs/plugins/fastdwarf.rst index 65d2a3d45..d964b319a 100644 --- a/docs/plugins/fastdwarf.rst +++ b/docs/plugins/fastdwarf.rst @@ -1,14 +1,36 @@ fastdwarf ========= -Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly -and perform tasks quickly. Teledwarf makes dwarves move instantaneously, -but do jobs at the same speed. - -:fastdwarf 0: disables both (also ``0 0``) -:fastdwarf 1: enables speedydwarf and disables teledwarf (also ``1 0``) -:fastdwarf 2: sets a native debug flag in the game memory that implements an - even more aggressive version of speedydwarf. -:fastdwarf 0 1: disables speedydwarf and enables teledwarf -:fastdwarf 1 1: enables both - -See `superdwarf` for a per-creature version. +Tags: +:dfhack-keybind:`fastdwarf` + +Dwarves teleport and/or finish jobs instantly. + +Usage: + +``enable fastdwarf`` + Enable the plugin. +``fastdwarf []`` + +Examples +-------- + +``fastdwarf 1`` + Make all your dwarves move and work at maximum speed. +``fastdwarf 1 1`` + In addition to working at maximum speed, dwarves also teleport to their + destinations. + +Options +------- + +Speed modes: + +:0: Dwarves move and work at normal rates. +:1: Dwarves move and work at maximum speed. +:2: ALL units move (and work) at maximum speed, including creatures and + hostiles. + +Tele modes: + +:0: No teleportation. +:1: Dwarves teleport to their destinations. diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 3948d8577..787f85c5d 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -224,20 +224,10 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("fastdwarf", - "let dwarves teleport and/or finish jobs instantly", - fastdwarf, false, - "fastdwarf: make dwarves faster.\n" - "Usage:\n" - " fastdwarf (tele)\n" - "Valid values for speed:\n" - " * 0 - Make dwarves move and work at standard speed.\n" - " * 1 - Make dwarves move and work at maximum speed.\n" - " * 2 - Make ALL creatures move and work at maximum speed.\n" - "Valid values for (tele):\n" - " * 0 - Disable dwarf teleportation (default)\n" - " * 1 - Make dwarves teleport to their destinations instantly.\n" - )); + commands.push_back(PluginCommand( + "fastdwarf", + "Dwarves teleport and/or finish jobs instantly.", + fastdwarf)); return CR_OK; } From ab9c3a07c4d83769e0e5bb3dc3952dec955d0b7d Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 10:22:42 -0700 Subject: [PATCH 327/854] add missing 'enable' usage for dwarfmonitor --- docs/plugins/dwarfmonitor.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/plugins/dwarfmonitor.rst b/docs/plugins/dwarfmonitor.rst index 6c1472c7d..75b71d06c 100644 --- a/docs/plugins/dwarfmonitor.rst +++ b/docs/plugins/dwarfmonitor.rst @@ -9,6 +9,8 @@ display widgets with live fort statistics. Usage: +``enable dwarfmonitor`` + Enable the plugin. ``dwarfmonitor enable `` Start tracking a specific facet of fortress life. The ``mode`` can be "work", "misery", "date", "weather", or "all". This will show the From 2654de583f13ecc080475ad45fc127be2dedd273 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 10:23:05 -0700 Subject: [PATCH 328/854] update docs for filltraffic --- docs/plugins/filltraffic.rst | 58 ++++++++++++++++++------------------ plugins/filltraffic.cpp | 44 ++++++++------------------- 2 files changed, 41 insertions(+), 61 deletions(-) diff --git a/docs/plugins/filltraffic.rst b/docs/plugins/filltraffic.rst index d3e952204..5056ec830 100644 --- a/docs/plugins/filltraffic.rst +++ b/docs/plugins/filltraffic.rst @@ -1,43 +1,43 @@ -.. _alltraffic: .. _restrictice: .. _restrictliquids: filltraffic =========== -Set traffic designations using flood-fill starting at the cursor. -See also `alltraffic`, `restrictice`, and `restrictliquids`. Options: +Tags: +:dfhack-keybind:`` -:H: High Traffic -:N: Normal Traffic -:L: Low Traffic -:R: Restricted Traffic -:X: Fill across z-levels. -:B: Include buildings and stockpiles. -:P: Include empty space. +Usage: + +``filltraffic []`` + Set traffic designations using flood-fill starting at the cursor. Flood + filling stops at walls and doors. +``alltraffic `` + Set traffic designations for every single tile of the map - useful for + resetting traffic designations. +``restrictice`` + Restrict traffic on all tiles on top of visible ice. +``restrictliquids`` + Restrict traffic on all visible tiles with liquid. -Example: +Examples +-------- ``filltraffic H`` - When used in a room with doors, it will set traffic to HIGH in just that room. + When used in a room with doors, it will set traffic to HIGH in just that + room. -alltraffic -========== -Set traffic designations for every single tile of the map - useful for resetting -traffic designations. See also `filltraffic`, `restrictice`, and `restrictliquids`. +Options +------- -Options: +Traffic designations: -:H: High Traffic -:N: Normal Traffic -:L: Low Traffic -:R: Restricted Traffic +:H: High Traffic. +:N: Normal Traffic. +:L: Low Traffic. +:R: Restricted Traffic. -restrictice -=========== -Restrict traffic on all tiles on top of visible ice. -See also `alltraffic`, `filltraffic`, and `restrictliquids`. +Filltraffic extra options: -restrictliquids -=============== -Restrict traffic on all visible tiles with liquid. -See also `alltraffic`, `filltraffic`, and `restrictice`. +:X: Fill across z-levels. +:B: Include buildings and stockpiles. +:P: Include empty space. diff --git a/plugins/filltraffic.cpp b/plugins/filltraffic.cpp index ddb5ef877..781fce0a8 100644 --- a/plugins/filltraffic.cpp +++ b/plugins/filltraffic.cpp @@ -44,41 +44,21 @@ DFHACK_PLUGIN("filltraffic"); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "filltraffic","Flood-fill selected traffic designation from cursor", - filltraffic, Gui::cursor_hotkey, - " Flood-fill selected traffic type from the cursor.\n" - "Traffic Type Codes:\n" - " H: High Traffic\n" - " N: Normal Traffic\n" - " L: Low Traffic\n" - " R: Restricted Traffic\n" - "Other Options:\n" - " X: Fill across z-levels.\n" - " B: Include buildings and stockpiles.\n" - " P: Include empty space.\n" - "Example:\n" - " filltraffic H\n" - " When used in a room with doors,\n" - " it will set traffic to HIGH in just that room.\n" - )); + "filltraffic", + "Flood-fill selected traffic designation from cursor.", + filltraffic, Gui::cursor_hotkey)); commands.push_back(PluginCommand( - "alltraffic","Set traffic for the entire map", - alltraffic, false, - " Set traffic types for all tiles on the map.\n" - "Traffic Type Codes:\n" - " H: High Traffic\n" - " N: Normal Traffic\n" - " L: Low Traffic\n" - " R: Restricted Traffic\n" - )); + "alltraffic", + "Set traffic designation for the entire map.", + alltraffic)); commands.push_back(PluginCommand( - "restrictliquids","Restrict on every visible square with liquid", - restrictLiquid, false, "" - )); + "restrictliquids", + "Restrict traffic on every visible square with liquid.", + restrictLiquid)); commands.push_back(PluginCommand( - "restrictice","Restrict traffic on squares above visible ice", - restrictIce, false, "" - )); + "restrictice", + "Restrict traffic on squares above visible ice.", + restrictIce)); return CR_OK; } From 9c0bd6bd709e7565373cf8fdfaa1e59c99af9577 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 10:23:26 -0700 Subject: [PATCH 329/854] update docs for fix-unit-occupancy --- docs/plugins/fix-unit-occupancy.rst | 44 ++++++++++++++++++++++------- plugins/fix-unit-occupancy.cpp | 14 ++------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/docs/plugins/fix-unit-occupancy.rst b/docs/plugins/fix-unit-occupancy.rst index bf999c8a3..48efd7255 100644 --- a/docs/plugins/fix-unit-occupancy.rst +++ b/docs/plugins/fix-unit-occupancy.rst @@ -1,12 +1,36 @@ fix-unit-occupancy ================== -This plugin fixes issues with unit occupancy, notably phantom -"unit blocking tile" messages (:bug:`3499`). It can be run manually, or -periodically when enabled with the built-in enable/disable commands: - -:(no argument): Run the plugin once immediately, for the whole map. -:-h, here, cursor: Run immediately, only operate on the tile at the cursor -:-n, dry, dry-run: Run immediately, do not write changes to map -:interval : Run the plugin every ``X`` ticks (when enabled). - The default is 1200 ticks, or 1 day. - Ticks are only counted when the game is unpaused. +Tags: +:dfhack-keybind:`` + +Fix phantom unit occupancy issues. For example, if you see "unit blocking tile" +messages that you can't account for (:bug:`3499`), this tool can help. + +Usage:: + + enable fix-unit-occupancy + fix-unit-occupancy [here] [-n] + fix-unit-occupancy interval + +When run without arguments (or with just the ``here`` or ``-n`` parameters), +the fix just runs once. You can also have it run periodically by enbling the +plugin. + +Examples +-------- + +``fix-unit-occupancy`` + Run once and fix all occupancy issues on the map. +``fix-unit-occupancy -n`` + Report on, but do not fix, all occupancy issues on the map. + +Options +------- + +``here`` + Only operate on the tile at the cursor. +``-n`` + Report issues, but do not any write changes to the map. +``interval `` + Set how often the plugin will check for and fix issues when it is enabled. + The default is 1200 ticks, or 1 game day. diff --git a/plugins/fix-unit-occupancy.cpp b/plugins/fix-unit-occupancy.cpp index d602ae213..973c185a8 100644 --- a/plugins/fix-unit-occupancy.cpp +++ b/plugins/fix-unit-occupancy.cpp @@ -181,18 +181,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector Date: Mon, 25 Jul 2022 10:54:11 -0700 Subject: [PATCH 330/854] update docs for fixveins --- docs/plugins/fixveins.rst | 12 +++++++++--- plugins/fixveins.cpp | 5 +++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/plugins/fixveins.rst b/docs/plugins/fixveins.rst index 58678d154..940c13b7a 100644 --- a/docs/plugins/fixveins.rst +++ b/docs/plugins/fixveins.rst @@ -1,5 +1,11 @@ fixveins ======== -Removes invalid references to mineral inclusions and restores missing ones. -Use this if you broke your embark with tools like `tiletypes`, or if you -accidentally placed a construction on top of a valuable mineral floor. +Tags: +:dfhack-keybind:`fixveins` + +Restore missing mineral inclusions. This tool can also remove invalid references +to mineral inclusions if you broke your embark with tools like `tiletypes`. + +Usage:: + + fixveins diff --git a/plugins/fixveins.cpp b/plugins/fixveins.cpp index 84d475518..f633f8422 100644 --- a/plugins/fixveins.cpp +++ b/plugins/fixveins.cpp @@ -103,8 +103,9 @@ command_result df_fixveins (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("fixveins", - "Remove invalid references to mineral inclusions and restore missing ones.", + commands.push_back(PluginCommand( + "fixveins", + "Restore missing mineral inclusions.", df_fixveins)); return CR_OK; } From e13aae2ce19e20c37850bac12c2c1e59d1f1803d Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 10:55:04 -0700 Subject: [PATCH 331/854] update docs for flows --- docs/plugins/flows.rst | 12 +++++++++--- plugins/flows.cpp | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/plugins/flows.rst b/docs/plugins/flows.rst index 3df451195..354b0078e 100644 --- a/docs/plugins/flows.rst +++ b/docs/plugins/flows.rst @@ -1,5 +1,11 @@ flows ===== -A tool for checking how many tiles contain flowing liquids. If you suspect that -your magma sea leaks into HFS, you can use this tool to be sure without -revealing the map. +Tags: +:dfhack-keybind:`flows` + +Counts map blocks with flowing liquids.. If you suspect that your magma sea +leaks into HFS, you can use this tool to be sure without revealing the map. + +Usage:: + + flows diff --git a/plugins/flows.cpp b/plugins/flows.cpp index 070776b10..a17554cb9 100644 --- a/plugins/flows.cpp +++ b/plugins/flows.cpp @@ -59,7 +59,8 @@ command_result df_flows (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("flows", + commands.push_back(PluginCommand( + "flows", "Counts map blocks with flowing liquids.", df_flows)); return CR_OK; From 20ccd3a99cd1937c5fb71c7de7c3a97fb2d9f5ec Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 10:55:31 -0700 Subject: [PATCH 332/854] update docs for follow --- docs/plugins/follow.rst | 13 ++++++++++--- plugins/follow.cpp | 9 ++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/plugins/follow.rst b/docs/plugins/follow.rst index a91c14f73..a0c15547e 100644 --- a/docs/plugins/follow.rst +++ b/docs/plugins/follow.rst @@ -1,5 +1,12 @@ follow ====== -Makes the game view follow the currently highlighted unit after you exit from the -current menu or cursor mode. Handy for watching dwarves running around. Deactivated -by moving the view manually. +Tags: +:dfhack-keybind:`follow` + +Make the screen follow the selected unit. Once you exit from the current menu or +cursor mode, the screen will stay centered on the unit. Handy for watching +dwarves running around. Deactivated by moving the cursor manually. + +Usage:: + + follow diff --git a/plugins/follow.cpp b/plugins/follow.cpp index e9733d5da..53ff5c523 100644 --- a/plugins/follow.cpp +++ b/plugins/follow.cpp @@ -31,11 +31,10 @@ uint8_t prevMenuWidth; DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "follow", "Make the screen follow the selected unit", - follow, Gui::view_unit_hotkey, - " Select a unit and run this plugin to make the camera follow it.\n" - " Moving the camera yourself deactivates the plugin.\n" - )); + "follow", + "Make the screen follow the selected unit.", + follow, + Gui::view_unit_hotkey)); followedUnit = 0; prevX=prevY=prevZ = -1; prevMenuWidth = 0; From 82953d8b2fe08fc0b31ca50b0eb09896189baa5f Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 13:37:52 -0700 Subject: [PATCH 333/854] fix doc build errors --- docs/Removed.rst | 8 ++++++++ docs/plugins/fastdwarf.rst | 7 +++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/Removed.rst b/docs/Removed.rst index 3dbe26820..a9d1d3470 100644 --- a/docs/Removed.rst +++ b/docs/Removed.rst @@ -49,6 +49,14 @@ existing .csv files. Just move them to the ``blueprints`` folder in your DF installation, and instead of ``digfort file.csv``, run ``quickfort run file.csv``. +.. _fix-armory: + +fix-armory +========== +Allowed the military to store equipment in barracks containers. Removed because +it required a binary patch to DF in order to function, and no such patch has +existed since DF 0.34.11. + .. _fortplan: fortplan diff --git a/docs/plugins/fastdwarf.rst b/docs/plugins/fastdwarf.rst index d964b319a..e06f2564c 100644 --- a/docs/plugins/fastdwarf.rst +++ b/docs/plugins/fastdwarf.rst @@ -5,11 +5,10 @@ Tags: Dwarves teleport and/or finish jobs instantly. -Usage: +Usage:: -``enable fastdwarf`` - Enable the plugin. -``fastdwarf []`` + enable fastdwarf + fastdwarf [] Examples -------- From eb0f016804953fc8fd5ac8dfa30c96430b616e68 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 16:40:19 -0700 Subject: [PATCH 334/854] update docs for forceequip --- docs/plugins/forceequip.rst | 101 +++++++++++++++++++++- plugins/forceequip.cpp | 165 +----------------------------------- 2 files changed, 100 insertions(+), 166 deletions(-) diff --git a/docs/plugins/forceequip.rst b/docs/plugins/forceequip.rst index 2278bca22..2b8a55905 100644 --- a/docs/plugins/forceequip.rst +++ b/docs/plugins/forceequip.rst @@ -1,7 +1,100 @@ forceequip ========== -Forceequip moves local items into a unit's inventory. It is typically used to -equip specific clothing/armor items onto a dwarf, but can also be used to put -armor onto a war animal or to add unusual items (such as crowns) to any unit. +Tags: +:dfhack-keybind:`forceequip` -For more information run ``forceequip help``. See also `modtools/equip-item`. +Move items into a unit's inventory. This tool is typically used to equip +specific clothing/armor items onto a dwarf, but can also be used to put armor +onto a war animal or to add unusual items (such as crowns) to any unit. Make +sure the unit you want to equip is standing on the target items, which must be +on the ground and be unforbidden. If multiple units are standing on the same +tile, the first one will be equipped. + +The most reliable way to set up the environment for this command is to pile +target items on a tile of floor with a garbage dump activity zone or the +`autodump` command, then walk/pasture a unit (or use `gui/teleport`) on top of +the items. Be sure to unforbid the items that you want to work with! + +.. note:: + + Weapons are not currently supported. + +Usage:: + + forceequip [] + +As mentioned above, this plugin can be used to equip items onto units (such as +animals) who cannot normally equip gear. There's an important caveat here: such +creatures will automatically drop inappropriate gear almost immediately (within +10 game ticks). If you want them to retain their equipment, you must forbid it +AFTER using forceequip to get it into their inventory. This technique can also +be used to clothe dwarven infants, but only if you're able to separate them from +their mothers. + +By default, the ``forceequip`` command will attempt to abide by game rules as +closely as possible. For instance, it will skip any item which is flagged for +use in a job, and will not equip more than one piece of clothing/armor onto any +given body part. These restrictions can be overridden via options, but doing so +puts you at greater risk of unexpected consequences. For instance, a dwarf who +is wearing three breastplates will not be able to move very quickly. + +Items equipped by this plugin DO NOT become owned by the recipient. Adult +dwarves are free to adjust their own wardrobe, and may promptly decide to doff +your gear in favour of their owned items. Animals, as described above, will tend +to discard ALL clothing immediately unless it is manually forbidden. Armor items +seem to be an exception: an animal will tend to retain an equipped suit of mail" + even if you neglect to forbid it. + +Please note that armored animals are quite vulnerable to ranged attacks. Unlike +dwarves, animals cannot block, dodge, or deflect arrows, and they are slowed by +the weight of their armor. + +Examples +-------- + +``forceequip`` + Attempts to equip all of the clothing and armor under the cursor onto the + unit under the cursor, following game rules regarding which item can be + equipped on which body part and only equipping 1 item onto each body part. + Items owned by other dwarves are ignored. +``forceequip v bp QQQ`` + List the bodyparts of the selected unit. +``forceequip bp LH`` + Equips an appopriate item onto the unit's left hand. +``forceequip m bp LH`` + Equips ALL appropriate items onto the unit's left hand. The unit may end up + wearing a dozen left-handed mittens. Use with caution, and remember that + dwarves tend to drop extra items ASAP. +``forceequip i bp NECK`` + Equips an item around the unit's neck, ignoring appropriateness + restrictions. If there's a millstone or an albatross carcass sitting on the + same square as the targeted unit, then there's a good chance that it will + end up around his neck. For precise control, remember that you can + selectively forbid some of the items that are piled on the ground. +``forceequip s`` + Equips the item currently selected in the k menu, if possible. +``forceequip s m i bp HD`` + Equips the selected item onto the unit's head. Ignores all restrictions and + conflicts. If you know exactly what you want to equip, and exactly where you + want it to go, then this is the most straightforward and reliable option. + +Options +------- + +``i``, ``ignore`` + Bypasses the usual item eligibility checks (such as "Never equip gear + belonging to another dwarf" and "Nobody is allowed to equip a Hive". +``m``, ``multi`` + Bypasses the 1-item-per-bodypart limit. Useful for equipping both a mitten + and a gauntlet on the same hand (or twelve breastplates on the upper body). +``m2``, ``m3``, ``m4`` + Modifies the 1-item-per-bodypart limit, allowing each part to receive 2, 3, + or 4 pieces of gear. +``s``, ``selected`` + Equip only the item currently selected in the k menu and ignore all other + items in the tile. +``bp``, ``bodypart `` + Specify which body part should be equipped. +``v``, ``verbose`` + Provide detailed narration and error messages, including listing available + body parts when an invalid ``bodypart`` code is specified. diff --git a/plugins/forceequip.cpp b/plugins/forceequip.cpp index ab39bb0d6..10a157608 100644 --- a/plugins/forceequip.cpp +++ b/plugins/forceequip.cpp @@ -55,171 +55,12 @@ const int const_GloveLeftHandedness = 2; command_result df_forceequip(color_ostream &out, vector & parameters); -const string forceequip_help = - "ForceEquip moves local items into a unit's inventory. It is typically\n" - "used to equip specific clothing/armor items onto a dwarf, but can also\n" - "be used to put armor onto a war animal or to add unusual items (such\n" - "as crowns) to any unit.\n" - "This plugin can process multiple items in a single call, but will only\n" - "work with a single unit (the first one it finds under the cursor).\n" - "In order to minimize confusion, it is recommended that you use\n" - "forceequip only when you have a unit standing alone atop a pile of\n" - "gear that you would like it to wear. Items which are stored in bins\n" - "or other containers (e.g. chests, armor racks) may also work, but\n" - "piling items on the floor (via a garbage dump activity zone, of the\n" - "DFHack autodump command) is the most reliable way to do it.\n" - "The plugin will ignore any items that are forbidden. Hence, you\n" - "can setup a large pile of surplus gear, walk a unit onto it (or\n" - "pasture an animal on it), unforbid a few items and run forceequip.\n" - "The (forbidden) majority of your gear will remain in-place, ready\n" - "for the next passerby." - "\n" - "As mentioned above, this plugin can be used to equip items onto\n" - "units (such as animals) which cannot normally equip gear. There's\n" - "an important caveat here - such creatures will automatically drop\n" - "inappropriate gear almost immediately (within 10 game ticks).\n" - "If you want them to retain their equipment, you must forbid it\n" - "AFTER using forceequip to get it into their inventory.\n" - "This technique can also be used to clothe dwarven infants, but\n" - "only if you're able to separate them from their mothers.\n" - "\n" - "By default, the forceequip plugin will attempt to avoid\n" - "conflicts and outright cheating. For instance, it will skip\n" - "any item which is flagged for use in a job, and will not\n" - "equip more than one piece of clothing/armor onto any given\n" - "body part. These restrictions can be overridden via command\n" - "switches (see examples below) but doing so puts you at greater\n" - "risk of unexpected consequences. For instance, a dwarf who\n" - "is wearing three breastplates will not be able to move very\n" - "quickly.\n" - "\n" - "Items equipped by this plugin DO NOT become owned by the\n" - "recipient. Adult dwarves are free to adjust their own\n" - "wardrobe, and may promptly decide to doff your gear in\n" - "favour of their owned items. Animals, as described above,\n" - "will tend to discard ALL clothing immediately unless it is\n" - "manually forbidden. Armor items seem to be an exception;\n" - "an animal will tend to retain an equipped suit of mail\n" - "even if you neglect to Forbid it.\n" - "\n" - "Please note that armored animals are quite vulnerable to ranged\n" - "attacks. Unlike dwarves, animals cannot block, dodge, or deflect\n" - "arrows, and they are slowed by the weight of their armor.\n" - "\n" - "This plugin currently does not support weapons.\n" - "\n" - "Options:\n" - " here, h - process the unit and item(s) under the cursor.\n" - " - This option is enabled by default since the plugin\n" - " - does not currently support remote equpping.\n" - " ignore, i - bypasses the usual item eligibility checks (such as\n" - " - \"Never equip gear belonging to another dwarf\" and\n" - " - \"Nobody is allowed to equip a Hive\".)\n" - " multi, m - bypasses the 1-item-per-bodypart limit, allowing\n" - " - the unit to receive an unlimited amount of gear.\n" - " - Can be used legitimately (e.g. mitten + gauntlet)\n" - " - or for cheating (e.g. twelve breastplates).\n" - " m2, m3, m4 - alters the 1-item-per-bodypart limit, allowing\n" - " - each part to receive 2, 3, or 4 pieces of gear.\n" - " selected, s - rather than processing all items piled at a unit's\n" - " - feet, process only the one item currently selected.\n" - " bodypart, bp - must be followed by a bodypart code (e.g. LH).\n" - " - Instructs the plugin to equip all available items\n" - " - onto this body part only. Typically used in\n" - " - conjunction with the f switch (to over-armor\n" - " - a particular bodypart) or the i switch (to equip\n" - " - an unusual item onto a specific slot).\n" - " verbose, v - provides detailed narration and error messages.\n" - " - Can be helpful in resolving failures; not needed\n" - " - for casual use.\n" - "\n" - "Examples:\n" - " forceequip\n" - " attempts to equip all of the items under the cursor onto the unit\n" - " under the cursor. Uses only clothing/armor items; ignores all\n" - " other types. Equips a maximum of 1 item onto each bodypart,\n" - " and equips only \"appropriate\" items in each slot (e.g. glove\n" - " --> hand). Bypasses any item which might cause a conflict,\n" - " such as a Boot belonging to a different dwarf.\n" - " forceequip bp LH\n" - " attempts to equip all local items onto the left hand of the local\n" - " unit. If the hand is already equipped then nothing will happen,\n" - " and if it is not equipped then only one appropriate item (e.g. \n" - " a single mitten or gauntlet) will be equipped. This command can\n" - " be useful if you don't want to selectively forbid individual items\n" - " and simply want the unit to equip, say, a Helmet while leaving\n" - " the rest of the pile alone.\n" - " forceequip m bp LH\n" - " as above, but will equip ALL appropriate items onto the unit's\n" - " left hand. After running this command, it might end up wearing\n" - " a dozen left-handed mittens. Use with caution, and remember\n" - " that dwarves will tend to drop supernumary items ASAP.\n" - " forceequip m\n" - " as above, but will equip ALL appropriate items onto any\n" - " appropriate bodypart. Tends to put several boots onto the right\n" - " foot while leaving the left foot bare.\n" - " forceequip m2\n" - " as above, but will equip up to two appropriate items onto each\n" - " bodypart. Helps to balance footwear, but doesn't ensure proper\n" - " placement (e.g. left foot gets two socks, right foot gets two\n" - " shoes). For best results, use \"selected bp LH\" and\n" - " \"selected bp RH\" instead.\n" - " forceequip i\n" - " performs the standard \"equip appropriate items onto appropriate\n" - " bodyparts\" logic, but also includes items that would normally\n" - " be considered ineligible (such as a sock which is owned by\n" - " a different dwarf).\n" - " forceequip bp NECK\n" - " attempts to equip any appropriate gear onto the Neck of the\n" - " local unit. Since the plugin believes that no items are actually\n" - " appropriate for the Neck slot, this command does nothing.\n" - " forceequip i bp NECK\n" - " attempts to equip items from the local pile onto the Neck\n" - " of the local unit. Ignores appropriateness restrictions.\n" - " If there's a millstone or an albatross carcass sitting on\n" - " the same square as the targeted unit, then there's a good\n" - " chance that it will end up around his neck. For precise\n" - " control, remember that you can selectively forbid some of\n" - " the items that are piled on the ground.\n" - " forceequip i m bp NECK\n" - " as above, but equips an unlimited number of items onto the\n" - " targeted bodypart. Effectively, all unforbidden items\n" - " (including helms, millstones, boulders, etc) will be\n" - " moved from the local pile and placed in the dwarf's\n" - " inventory (specifically, on his neck). When used with\n" - " a large pile of goods, this will leave the dwarf heavily\n" - " encumbered and very slow to move.\n" - " forceequip s\n" - " requires that a single item be selected using the k menu.\n" - " This item must occupy the same square as the target unit,\n" - " and must be unforbidden. Attempts to equip this single\n" - " item onto an appropriate slot in the unit's inventory.\n" - " Can serve as a quicker alternative to the selective-\n" - " unforbidding approach described above.\n" - " forceequip s m i bp HD\n" - " equips the selected item onto the unit's head. Ignores\n" - " all possible restrictions and conflicts. If you know\n" - " exactly what you want to equip, and exactly where you\n" - " want it to go, then this is the most straightforward\n" - " and reliable option.\n" - " forceequip v bp QQQ\n" - " guaranteed to fail (and accomplish nothing) because\n" - " there are no bodyparts called QQQ. However, since the\n" - " verbose switch is used, the resulting error messages\n" - " will list every bodypart that the unit DOES possess.\n" - " May be useful if you're unfamiliar with the BP codes\n" - " used by Dwarf Fortress, or if you're experimenting\n" - " with an exotic creature.\n" - "\n" - ; - DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( - "forceequip", "Move items from the ground into a unit's inventory", - df_forceequip, false, - forceequip_help.c_str() - )); + "forceequip", + "Move items into a unit's inventory.", + df_forceequip)); return CR_OK; } From 367ac0064ef21089a7eca07752f3417fba6eb977 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 16:54:28 -0700 Subject: [PATCH 335/854] update docs for generated-creature-renamer --- docs/plugins/generated-creature-renamer.rst | 28 +++++++++++++-------- plugins/generated-creature-renamer.cpp | 13 +++------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/docs/plugins/generated-creature-renamer.rst b/docs/plugins/generated-creature-renamer.rst index bb486a4ee..1615ba32d 100644 --- a/docs/plugins/generated-creature-renamer.rst +++ b/docs/plugins/generated-creature-renamer.rst @@ -1,15 +1,23 @@ generated-creature-renamer ========================== -Automatically renames generated creatures, such as forgotten beasts, titans, -etc, to have raw token names that match the description given in-game. +Tags: +:dfhack-keybind:`list-generated` +:dfhack-keybind:`save-generated-raws` -The ``list-generated`` command can be used to list the token names of all -generated creatures in a given save, with an optional ``detailed`` argument -to show the accompanying description. +Automatically renames generated creatures. Now, forgotten beasts, titans, +necromancer experiments, etc. will have raw token names that match the +description given in-game instead of unreadable generated strings. -The ``save-generated-raws`` command will save a sample creature graphics file in -the Dwarf Fortress root directory, to use as a start for making a graphics set -for generated creatures using the new names that they get with this plugin. +Usage: -The new names are saved with the save, and the plugin, when enabled, only runs once -per save, unless there's an update. +``enable generated-creature-renamer`` + Rename generated creatures when a world is loaded. +``list-generated [detailed]`` + List the token names of all generated creatures in the loaded save. If + ``detailed`` is specified, then also show the accompanying description. +``save-generated-raws`` + Save a sample creature graphics file in the Dwarf Fortress root directory to + use as a start for making a graphics set for generated creatures using the + new names that they get with this plugin. + +The new names are saved with the world. diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 625c093cf..f6e9e41b6 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -27,17 +27,12 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Mon, 25 Jul 2022 16:57:08 -0700 Subject: [PATCH 336/854] fix typos in forceequip cpp/docs --- docs/plugins/forceequip.rst | 4 ++-- plugins/forceequip.cpp | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/plugins/forceequip.rst b/docs/plugins/forceequip.rst index 2b8a55905..217085fe4 100644 --- a/docs/plugins/forceequip.rst +++ b/docs/plugins/forceequip.rst @@ -42,8 +42,8 @@ Items equipped by this plugin DO NOT become owned by the recipient. Adult dwarves are free to adjust their own wardrobe, and may promptly decide to doff your gear in favour of their owned items. Animals, as described above, will tend to discard ALL clothing immediately unless it is manually forbidden. Armor items -seem to be an exception: an animal will tend to retain an equipped suit of mail" - even if you neglect to forbid it. +seem to be an exception: an animal will tend to retain an equipped suit of mail +even if you neglect to forbid it. Please note that armored animals are quite vulnerable to ranged attacks. Unlike dwarves, animals cannot block, dodge, or deflect arrows, and they are slowed by diff --git a/plugins/forceequip.cpp b/plugins/forceequip.cpp index 10a157608..2ee4e47a7 100644 --- a/plugins/forceequip.cpp +++ b/plugins/forceequip.cpp @@ -280,8 +280,7 @@ command_result df_forceequip(color_ostream &out, vector & parameters) if (p == "help" || p == "?" || p == "h" || p == "/?" || p == "info" || p == "man") { - out << forceequip_help << endl; - return CR_OK; + return CR_WRONG_USAGE; } else if (p == "here" || p == "h") { From 8cf7f59c398cbc25de7e471052e9ed4e63d97155 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 17:48:39 -0700 Subject: [PATCH 337/854] wrap ls and tags output --- library/lua/helpdb.lua | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 5e0f49920..1bf93f333 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -653,21 +653,31 @@ function help(entry) print(get_entry_long_help(entry)) end -local function get_max_width(list, min_width) - local width = min_width or 0 - for _,item in ipairs(list) do - width = math.max(width, #item) +-- prints col1 (width 20), a 2 space gap, and col2 (width 58) +-- if col1text is longer than 20 characters, col2text is printed on the next +-- line. if col2text is longer than 58 characters, it is wrapped. +local COL1WIDTH, COL2WIDTH = 20, 58 +local function print_columns(col1text, col2text) + col2text = col2text:wrap(COL2WIDTH) + local wrapped_col2 = {} + for line in col2text:gmatch('[^'..NEWLINE..']*') do + table.insert(wrapped_col2, line) + end + if #col1text > COL1WIDTH then + print(col1text) + else + print(('%-'..COL1WIDTH..'s %s'):format(col1text, wrapped_col2[1])) + end + for i=2,#wrapped_col2 do + print(('%'..COL1WIDTH..'s %s'):format(' ', wrapped_col2[i])) end - return width end -- implements the 'tags' builtin command function tags() local tags = get_tags() - local width = get_max_width(tags, 10) for _,tag in ipairs(tags) do - print((' %-'..width..'s %s'):format(tag, - get_tag_data(tag).description)) + print_columns(tag, get_tag_data(tag).description) end end @@ -675,10 +685,8 @@ end -- defined as in search_entries() above. function list_entries(skip_tags, include, exclude) local entries = search_entries(include, exclude) - local width = get_max_width(entries, 10) for _,entry in ipairs(entries) do - print((' %-'..width..'s %s'):format( - entry, get_entry_short_help(entry))) + print_columns(entry, get_entry_short_help(entry)) if not skip_tags then local tags = get_entry_tags(entry) if #tags > 0 then From b93e3365f6d8fb1281d8de49c680a38d1fe084be Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 21:50:52 -0700 Subject: [PATCH 338/854] update docs for get plants --- docs/plugins/getplants.rst | 69 ++++++++++++++++++++++++-------------- plugins/getplants.cpp | 21 ++---------- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/docs/plugins/getplants.rst b/docs/plugins/getplants.rst index 5a719b3ab..167ac72f2 100644 --- a/docs/plugins/getplants.rst +++ b/docs/plugins/getplants.rst @@ -1,24 +1,48 @@ getplants ========= -This tool allows plant gathering and tree cutting by RAW ID. Specify the types -of trees to cut down and/or shrubs to gather by their plant names, separated -by spaces. - -Options: - -:``-t``: Tree: Select trees only (exclude shrubs) -:``-s``: Shrub: Select shrubs only (exclude trees) -:``-f``: Farming: Designate only shrubs that yield seeds for farming. Implies -s -:``-c``: Clear: Clear designations instead of setting them -:``-x``: eXcept: Apply selected action to all plants except those specified (invert - selection) -:``-a``: All: Select every type of plant (obeys ``-t``/``-s``/``-f``) -:``-v``: Verbose: Lists the number of (un)designations per plant -:``-n *``: Number: Designate up to * (an integer number) plants of each species - -Specifying both ``-t`` and ``-s`` or ``-f`` will have no effect. If no plant IDs are -specified, all valid plant IDs will be listed, with ``-t``, ``-s``, and ``-f`` -restricting the list to trees, shrubs, and farmable shrubs, respectively. +Tags: +:dfhack-keybind:`getplants` + +Designate trees for chopping and shrubs for gathering. Specify the types of +trees to cut down and/or shrubs to gather by their plant names. + +Usage: + +``getplants [-t|-s|-f]`` + List valid tree/shrub ids, optionally restricted to the specified type. +``getplants [ ...] []`` + Designate trees/shrubs of the specified types for chopping/gathering. + +Examples +-------- + +``getplants`` + List all valid IDs. +``getplants -f -a`` + Gather all plants on the map that yield seeds for farming. +``getplants NETHER_CAP -n 10`` + Designate 10 nether cap trees for chopping. + +Options +------- + +``-t`` + Tree: Select trees only (exclude shrubs). +``-s`` + Shrub: Select shrubs only (exclude trees). +``-f`` + Farming: Designate only shrubs that yield seeds for farming. +``-a`` + All: Select every type of plant (obeys ``-t``/``-s``/``-f``). +``-c`` + Clear: Clear designations instead of setting them. +``-x`` + eXcept: Apply selected action to all plants except those specified (invert + selection). +``-v`` + Verbose: Lists the number of (un)designations per plant. +``-n `` + Number: Designate up to the specified number of plants of each species. .. note:: @@ -28,10 +52,3 @@ restricting the list to trees, shrubs, and farmable shrubs, respectively. This leads to some shrubs being designated when they shouldn't be, causing a plant gatherer to walk there and do nothing (except clearing the designation). See :issue:`1479` for details. - - The implementation another known deficiency: it's incapable of detecting that - raw definitions that specify a seed extraction reaction for the structural part - but has no other use for it cannot actually yield any seeds, as the part is - never used (parts of :bug:`6940`, e.g. Red Spinach), even though DF - collects it, unless there's a workshop reaction to do it (which there isn't - in vanilla). diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 25823eb17..6819b6191 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -584,24 +584,9 @@ command_result df_getplants (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( - "getplants", "Cut down trees or gather shrubs by ID", - df_getplants, false, - " Specify the types of trees to cut down and/or shrubs to gather by their\n" - " plant IDs, separated by spaces.\n" - "Options:\n" - " -t - Tree: Select trees only (exclude shrubs)\n" - " -s - Shrub: Select shrubs only (exclude trees)\n" - " -f - Farming: Designate only shrubs that yield seeds for farming. Implies -s\n" - " -c - Clear: Clear designations instead of setting them\n" - " -x - eXcept: Apply selected action to all plants except those specified\n" - " -a - All: Select every type of plant (obeys -t/-s/-f)\n" - " -v - Verbose: List the number of (un)designations per plant\n" - " -n * - Number: Designate up to * (an integer number) plants of each species\n" - "Specifying both -t and -s or -f will have no effect.\n" - "If no plant IDs are specified, and the -a switch isn't given, all valid plant\n" - "IDs will be listed with -t, -s, and -f restricting the list to trees, shrubs,\n" - "and farmable shrubs, respectively.\n" - )); + "getplants", + "Designate trees for chopping and shrubs for gathering.", + df_getplants)); return CR_OK; } From 69d88a62ddd27c54d1a484808a451ed2153ade99 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 21:54:34 -0700 Subject: [PATCH 339/854] update docs for hotkeys --- docs/plugins/hotkeys.rst | 14 ++++++++++---- plugins/hotkeys.cpp | 5 +++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/plugins/hotkeys.rst b/docs/plugins/hotkeys.rst index 57c3b3af5..49064db0a 100644 --- a/docs/plugins/hotkeys.rst +++ b/docs/plugins/hotkeys.rst @@ -1,8 +1,14 @@ hotkeys ======= -Opens an in-game screen showing which DFHack keybindings are -active in the current context. See also `hotkey-notes`. +Tags: +:dfhack-keybind:`hotkeys` -.. image:: ../images/hotkeys.png +Show all dfhack keybindings in current context. The command opens an in-game +screen showing which DFHack keybindings are active in the current context. +See also `hotkey-notes`. -:dfhack-keybind:`hotkeys` +Usage:: + + hotkeys + +.. image:: ../images/hotkeys.png diff --git a/plugins/hotkeys.cpp b/plugins/hotkeys.cpp index a91c62bdf..84ce37eb8 100644 --- a/plugins/hotkeys.cpp +++ b/plugins/hotkeys.cpp @@ -356,8 +356,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Mon, 25 Jul 2022 22:00:56 -0700 Subject: [PATCH 340/854] update docs for infiniteSky --- docs/plugins/infiniteSky.rst | 18 ++++++++++++------ plugins/infiniteSky.cpp | 14 +------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/docs/plugins/infiniteSky.rst b/docs/plugins/infiniteSky.rst index 328ee83bd..24e206f25 100644 --- a/docs/plugins/infiniteSky.rst +++ b/docs/plugins/infiniteSky.rst @@ -1,14 +1,20 @@ infiniteSky =========== -Automatically allocates new z-levels of sky at the top of the map as you build up, -or on request allocates many levels all at once. +Tags: +:dfhack-keybind:`` + +Automatically allocates new z-levels of sky at the top of the map as you build +up, or on request allocates many levels all at once. Usage: -``infiniteSky n`` - Raise the sky by n z-levels. -``infiniteSky enable/disable`` - Enables/disables monitoring of constructions. If you build anything in the second to highest z-level, it will allocate one more sky level. This is so you can continue to build stairs upward. +``enable infiniteSky`` + Enables monitoring of constructions. If you build anything in the second + highest z-level, it will allocate one more sky level. You can build stairs + up as high as you like! +``infiniteSky []`` + Raise the sky by n z-levels. If run without parameters, raises the sky by + one z-level. .. warning:: diff --git a/plugins/infiniteSky.cpp b/plugins/infiniteSky.cpp index d63eec6b8..bc51f06f1 100644 --- a/plugins/infiniteSky.cpp +++ b/plugins/infiniteSky.cpp @@ -33,19 +33,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Mon, 25 Jul 2022 22:01:28 -0700 Subject: [PATCH 341/854] add missing keybinding for infiniteSky --- docs/plugins/infiniteSky.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/infiniteSky.rst b/docs/plugins/infiniteSky.rst index 24e206f25..96f141de5 100644 --- a/docs/plugins/infiniteSky.rst +++ b/docs/plugins/infiniteSky.rst @@ -1,7 +1,7 @@ infiniteSky =========== Tags: -:dfhack-keybind:`` +:dfhack-keybind:`infiniteSky` Automatically allocates new z-levels of sky at the top of the map as you build up, or on request allocates many levels all at once. From c88423e65563ed66b06f7f1bc4afc6c2c60f3296 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 25 Jul 2022 22:05:32 -0700 Subject: [PATCH 342/854] remove docs for non-enableable, command-less plugin --- docs/plugins/isoworldremote.rst | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 docs/plugins/isoworldremote.rst diff --git a/docs/plugins/isoworldremote.rst b/docs/plugins/isoworldremote.rst deleted file mode 100644 index 7bacc2b57..000000000 --- a/docs/plugins/isoworldremote.rst +++ /dev/null @@ -1,3 +0,0 @@ -isoworldremote -============== -A plugin that implements a `remote API ` used by Isoworld. From 5b0f9ddd4f94e8607ff1987a4dbccd03cc4cf909 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 26 Jul 2022 10:24:05 -0700 Subject: [PATCH 343/854] bump the default history size to 5000 100 is just too small, especially since we're not removing duplicate entries. --- library/include/Console.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/include/Console.h b/library/include/Console.h index 0882ba449..f0f56ca85 100644 --- a/library/include/Console.h +++ b/library/include/Console.h @@ -44,7 +44,7 @@ namespace DFHack class CommandHistory { public: - CommandHistory(std::size_t capacity = 100) + CommandHistory(std::size_t capacity = 5000) { this->capacity = capacity; } From 0fae25fc8b505bcc49dae7693b054fdcc289ddf3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2022 00:47:38 -0400 Subject: [PATCH 344/854] Only write (most) generated rst files if they actually changed Speeds up incremental builds significantly --- conf.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/conf.py b/conf.py index 814c7c2cb..f13117484 100644 --- a/conf.py +++ b/conf.py @@ -14,7 +14,9 @@ serve to show the default. # pylint:disable=redefined-builtin +import contextlib import datetime +import io from io import open import os import re @@ -132,13 +134,26 @@ def get_open_mode(): return 'w' if sys.version_info.major > 2 else 'wb' +@contextlib.contextmanager +def write_file_if_changed(path): + with io.StringIO() as buffer: + yield buffer + new_contents = buffer.getvalue() + + with open(path, 'r') as infile: + old_contents = infile.read() + + if old_contents != new_contents: + with open(path, get_open_mode()) as outfile: + outfile.write(new_contents) + + def generate_tag_indices(): os.makedirs('docs/tags', mode=0o755, exist_ok=True) - with open('docs/tags/index.rst', get_open_mode()) as topidx: + with write_file_if_changed('docs/tags/index.rst') as topidx: for tag_tuple in get_tags(): tag = tag_tuple[0] - with open(('docs/tags/{name}.rst').format(name=tag), - get_open_mode()) as tagidx: + with write_file_if_changed(('docs/tags/{name}.rst').format(name=tag)) as tagidx: tagidx.write('TODO: add links to the tools that have this tag') topidx.write(('.. _tag/{name}:\n\n').format(name=tag)) topidx.write(('{name}\n').format(name=tag)) @@ -168,7 +183,7 @@ def write_tool_docs(): # the page in one long wrapped line, similar to how the wiki does it os.makedirs(os.path.join('docs/tools', os.path.dirname(k[0])), mode=0o755, exist_ok=True) - with open('docs/tools/{}.rst'.format(k[0]), get_open_mode()) as outfile: + with write_file_if_changed('docs/tools/{}.rst'.format(k[0])) as outfile: outfile.write(header) if k[0] != 'search' and k[0] != 'stonesense': outfile.write(label) From a8b40c5911ad8d028849fab43e5d38999735bb94 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2022 00:48:22 -0400 Subject: [PATCH 345/854] Drop Python 2 compat --- conf.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/conf.py b/conf.py index f13117484..3bb73d40d 100644 --- a/conf.py +++ b/conf.py @@ -17,7 +17,6 @@ serve to show the default. import contextlib import datetime import io -from io import open import os import re import shlex # pylint:disable=unused-import @@ -130,10 +129,6 @@ def get_tags(): return tags -def get_open_mode(): - return 'w' if sys.version_info.major > 2 else 'wb' - - @contextlib.contextmanager def write_file_if_changed(path): with io.StringIO() as buffer: @@ -144,7 +139,7 @@ def write_file_if_changed(path): old_contents = infile.read() if old_contents != new_contents: - with open(path, get_open_mode()) as outfile: + with open(path, 'w') as outfile: outfile.write(new_contents) From 6293b71e9e3b821a8e43ccb65a4dfe3c4a2be335 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 26 Jul 2022 22:01:39 -0700 Subject: [PATCH 346/854] Revert "remove docs for non-enableable, command-less plugin" This reverts commit c88423e65563ed66b06f7f1bc4afc6c2c60f3296. --- docs/plugins/isoworldremote.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/plugins/isoworldremote.rst diff --git a/docs/plugins/isoworldremote.rst b/docs/plugins/isoworldremote.rst new file mode 100644 index 000000000..7bacc2b57 --- /dev/null +++ b/docs/plugins/isoworldremote.rst @@ -0,0 +1,3 @@ +isoworldremote +============== +A plugin that implements a `remote API ` used by Isoworld. From 671f10e5d835824c3dd78c0a142ee6dbc870e925 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2022 01:04:47 -0400 Subject: [PATCH 347/854] Fix write_file_if_changed() if target file does not exist --- conf.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/conf.py b/conf.py index 3bb73d40d..fab4b286a 100644 --- a/conf.py +++ b/conf.py @@ -135,8 +135,11 @@ def write_file_if_changed(path): yield buffer new_contents = buffer.getvalue() - with open(path, 'r') as infile: - old_contents = infile.read() + try: + with open(path, 'r') as infile: + old_contents = infile.read() + except IOError: + old_contents = None if old_contents != new_contents: with open(path, 'w') as outfile: From 10f8417cc275b6fb597c56a3e1b3563018ca15d9 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 26 Jul 2022 22:22:31 -0700 Subject: [PATCH 348/854] create helpdb entries for all plugins even those that have no commands and are not enableable --- library/LuaApi.cpp | 16 ---------------- library/lua/helpdb.lua | 23 ++++++++--------------- 2 files changed, 8 insertions(+), 31 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 4ee1a4d1c..d04da21ec 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -3164,21 +3164,6 @@ static int internal_getCommandDescription(lua_State *L) return 1; } -static int internal_isPluginEnableable(lua_State *L) -{ - auto plugins = Core::getInstance().getPluginManager(); - - const char *name = luaL_checkstring(L, 1); - - auto plugin = plugins->getPluginByName(name); - if (plugin) - lua_pushboolean(L, plugin->can_be_enabled()); - else - lua_pushnil(L); - - return 1; -} - static int internal_threadid(lua_State *L) { std::stringstream ss; @@ -3255,7 +3240,6 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "listCommands", internal_listCommands }, { "getCommandHelp", internal_getCommandHelp }, { "getCommandDescription", internal_getCommandDescription }, - { "isPluginEnableable", internal_isPluginEnableable }, { "threadid", internal_threadid }, { "md5File", internal_md5file }, { NULL, NULL } diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 1bf93f333..2aee005ee 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -273,13 +273,11 @@ local function update_db(old_db, entry_name, text_entry, help_source, kwargs) -- already in db (e.g. from a higher-priority script dir); skip return end - if not kwargs.text_entry_only then - entrydb[entry_name] = { - entry_types=kwargs.entry_types, - short_help=kwargs.short_help, - text_entry=text_entry - } - end + entrydb[entry_name] = { + entry_types=kwargs.entry_types, + short_help=kwargs.short_help, + text_entry=text_entry + } if entry_name ~= text_entry then return end @@ -317,7 +315,7 @@ local function scan_plugins(old_db) local plugin_names = dfhack.internal.listPlugins() for _,plugin in ipairs(plugin_names) do local commands = dfhack.internal.listCommands(plugin) - local includes_plugin, has_commands = false, false + local includes_plugin = false for _,command in ipairs(commands) do local kwargs = {entry_types={[ENTRY_TYPES.COMMAND]=true}} if command == plugin then @@ -329,18 +327,13 @@ local function scan_plugins(old_db) has_rendered_help(plugin) and HELP_SOURCES.RENDERED or HELP_SOURCES.PLUGIN, kwargs) - has_commands = true end - if includes_plugin then goto continue end - local is_enableable = dfhack.internal.isPluginEnableable(plugin) - if has_commands or is_enableable then + if not includes_plugin then update_db(old_db, plugin, plugin, has_rendered_help(plugin) and HELP_SOURCES.RENDERED or HELP_SOURCES.STUB, - {entry_types={[ENTRY_TYPES.PLUGIN]=true}, - text_entry_only=not is_enableable}) + {entry_types={[ENTRY_TYPES.PLUGIN]=true}}) end - ::continue:: end end From 474dae21c678a91e43c27f2e142b713952024105 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 27 Jul 2022 07:18:22 +0000 Subject: [PATCH 349/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 78b7554a1..b9d49dfc3 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 78b7554a17d7d385986b2f801d5791485e740afb +Subproject commit b9d49dfc36dae9b8b1bf91c2a8e8179ca554abdc From e677e8098d8c7a353ca5af2c8e3614268fd214cc Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 27 Jul 2022 15:48:11 -0700 Subject: [PATCH 350/854] add docs for plugins that only provide Lua apis --- docs/Lua API.rst | 14 +++++++------- docs/plugins/building-hacks.rst | 6 ++++++ docs/plugins/cxxrandom.rst | 6 ++++++ docs/plugins/eventful.rst | 6 ++++++ docs/plugins/luasocket.rst | 6 ++++++ docs/plugins/map-render.rst | 6 ++++++ docs/plugins/pathable.rst | 6 ++++++ docs/plugins/xlsxreader.rst | 6 ++++++ 8 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 docs/plugins/building-hacks.rst create mode 100644 docs/plugins/cxxrandom.rst create mode 100644 docs/plugins/eventful.rst create mode 100644 docs/plugins/luasocket.rst create mode 100644 docs/plugins/map-render.rst create mode 100644 docs/plugins/pathable.rst create mode 100644 docs/plugins/xlsxreader.rst diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 107a0ba2e..94eaa1165 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4287,7 +4287,7 @@ blueprint files: The names of the functions are also available as the keys of the ``valid_phases`` table. -.. _building-hacks: +.. _building-hacks-api: building-hacks ============== @@ -4426,7 +4426,7 @@ Native functions: The lua module file also re-exports functions from ``dfhack.burrows``. -.. _cxxrandom: +.. _cxxrandom-api: cxxrandom ========= @@ -4596,7 +4596,7 @@ The dig-now plugin exposes the following functions to Lua: command ``dig-now ``. See the `dig-now` documentation for details on default settings. -.. _eventful: +.. _eventful-api: eventful ======== @@ -4764,7 +4764,7 @@ Integrated tannery:: b=require "plugins.eventful" b.addReactionToShop("TAN_A_HIDE","LEATHERWORKS") -.. _luasocket: +.. _luasocket-api: luasocket ========= @@ -4835,7 +4835,7 @@ A class with all the tcp functionality. Tries connecting to that address and port. Returns ``client`` object. -.. _map-render: +.. _map-render-api: map-render ========== @@ -4851,7 +4851,7 @@ Functions returns a table with w*h*4 entries of rendered tiles. The format is same as ``df.global.gps.screen`` (tile,foreground,bright,background). -.. _pathable: +.. _pathable-api: pathable ======== @@ -4885,7 +4885,7 @@ sort The `sort ` plugin does not export any native functions as of now. Instead, it calls Lua code to perform the actual ordering of list items. -.. _xlsxreader: +.. _xlsxreader-api: xlsxreader ========== diff --git a/docs/plugins/building-hacks.rst b/docs/plugins/building-hacks.rst new file mode 100644 index 000000000..106173c47 --- /dev/null +++ b/docs/plugins/building-hacks.rst @@ -0,0 +1,6 @@ +building-hacks +============== + +Provides a Lua API for creating powered workshops. + +See `building-hacks-api` for more details. diff --git a/docs/plugins/cxxrandom.rst b/docs/plugins/cxxrandom.rst new file mode 100644 index 000000000..f3ed8f86d --- /dev/null +++ b/docs/plugins/cxxrandom.rst @@ -0,0 +1,6 @@ +cxxrandom +========= + +Provides a Lua API for random distributions. + +See `cxxrandom-api` for details. diff --git a/docs/plugins/eventful.rst b/docs/plugins/eventful.rst new file mode 100644 index 000000000..892f233bb --- /dev/null +++ b/docs/plugins/eventful.rst @@ -0,0 +1,6 @@ +eventful +======== + +Provides a Lua API for reacting to in-game events. + +See `eventful-api` for details. diff --git a/docs/plugins/luasocket.rst b/docs/plugins/luasocket.rst new file mode 100644 index 000000000..6f7c96cae --- /dev/null +++ b/docs/plugins/luasocket.rst @@ -0,0 +1,6 @@ +luasocket +========= + +Provides a Lua API for accessing network sockets. + +See `luasocket-api` for details. diff --git a/docs/plugins/map-render.rst b/docs/plugins/map-render.rst new file mode 100644 index 000000000..2bcb32adf --- /dev/null +++ b/docs/plugins/map-render.rst @@ -0,0 +1,6 @@ +map-render +========== + +Provides a Lua API for rerendering portions of the map. + +See `map-render-api` for details. diff --git a/docs/plugins/pathable.rst b/docs/plugins/pathable.rst new file mode 100644 index 000000000..daf7697bc --- /dev/null +++ b/docs/plugins/pathable.rst @@ -0,0 +1,6 @@ +pathable +======== + +Provides a Lua API for marking tiles that are reachable from the cursor. + +See `pathable-api` for details. diff --git a/docs/plugins/xlsxreader.rst b/docs/plugins/xlsxreader.rst new file mode 100644 index 000000000..6429ceedc --- /dev/null +++ b/docs/plugins/xlsxreader.rst @@ -0,0 +1,6 @@ +xlsxreader +========== + +Provides a Lua API for reading .xlsx files. + +See `xlsxreader-api` for details. From 117efaa8147fc3b22fbf94a7562ecb0ecc5213f8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 27 Jul 2022 15:48:44 -0700 Subject: [PATCH 351/854] update short description for command-prompt --- plugins/command-prompt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/command-prompt.cpp b/plugins/command-prompt.cpp index 1994d66b5..94398837a 100644 --- a/plugins/command-prompt.cpp +++ b/plugins/command-prompt.cpp @@ -345,7 +345,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Wed, 27 Jul 2022 16:03:49 -0700 Subject: [PATCH 352/854] update docs for jobutils. I was unable to find an example usage for `job item-type`. I couldn't find any combination of jobs or parameters that worked. --- docs/plugins/jobutils.rst | 71 +++++++++++++++++++++------------------ plugins/jobutils.cpp | 42 ++++++----------------- 2 files changed, 49 insertions(+), 64 deletions(-) diff --git a/docs/plugins/jobutils.rst b/docs/plugins/jobutils.rst index 230c69f4c..8863c7a9b 100644 --- a/docs/plugins/jobutils.rst +++ b/docs/plugins/jobutils.rst @@ -1,40 +1,45 @@ .. _job: -job -=== -Command for general job query and manipulation. +jobutils +======== +Tags: +:dfhack-keybind:`job` +:dfhack-keybind:`job-duplicate` +:dfhack-keybind:`job-material` -Options: +Usage: -*no extra options* - Print details of the current job. The job can be selected - in a workshop, or the unit/jobs screen. -**list** +``job`` + Print details of the current job. The job can be selected in a workshop or + the unit/jobs screen. +``job list`` Print details of all jobs in the selected workshop. -**item-material ** +``job item-material `` Replace the exact material id in the job item. -**item-type ** +``job item-type `` Replace the exact item type id in the job item. - -job-duplicate -============= -In :kbd:`q` mode, when a job is highlighted within a workshop or furnace -building, calling ``job-duplicate`` instantly duplicates the job. - -:dfhack-keybind:`job-duplicate` - -job-material -============ -Alter the material of the selected job. Similar to ``job item-material ...`` - -Invoked as:: - - job-material - -:dfhack-keybind:`job-material` - -* In :kbd:`q` mode, when a job is highlighted within a workshop or furnace, - changes the material of the job. Only inorganic materials can be used - in this mode. -* In :kbd:`b` mode, during selection of building components positions the cursor - over the first available choice with the matching material. +``job-duplicate`` + Duplicates the highlighted job. Must be in :kbd:`q` mode and have a workshop + or furnace building selected. +``job-material `` + Alters the material of the selected job (in :kbd:`q` mode) or jumps to the + selected material when choosing the building component of a planned building + (in :kbd:`b` mode). Note that this form of the command can only handle + inorganic materials. + +Use the ``job`` and ``job list`` commands to discover the type and material ids +for existing jobs, or use the following commands to see the full lists:: + + lua @df.item_type + lua "for i,mat in ipairs(df.global.world.raws.inorganics) do if mat.material.flags.IS_STONE and not mat.material.flags.NO_STONE_STOCKPILE then print(i, mat.id) end end" + +Examples +-------- + +``job-material GNEISS`` + Change the selected "Construct rock Coffin" job at a Mason's workshop to + "Construct gneiss coffin". +``job item-material 2 MARBLE`` + Change the selected "Construct Traction Bench" job (which has three source + items: a table, a mechanism, and a chain) to specifically use a marble + mechanism. diff --git a/plugins/jobutils.cpp b/plugins/jobutils.cpp index 0607b50c5..5f5994435 100644 --- a/plugins/jobutils.cpp +++ b/plugins/jobutils.cpp @@ -53,46 +53,26 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector \n" - " Replace the exact material id in the job item.\n" - " job item-type \n" - " Replace the exact item type id in the job item.\n" - ) - ); + "job", + "Inspect and modify job item properties.", + job_cmd)); if (ui_workshop_job_cursor || ui_build_selector) { commands.push_back( PluginCommand( - "job-material", "Alter the material of the selected job.", - job_material, job_material_hotkey, - " job-material \n" - "Intended to be used as a keybinding:\n" - " - In 'q' mode, when a job is highlighted within a workshop\n" - " or furnace, changes the material of the job. Only inorganic\n" - " materials can be used in this mode.\n" - " - In 'b' mode, during selection of building components\n" - " positions the cursor over the first available choice\n" - " with the matching material.\n" - ) - ); + "job-material", + "Alter the material of the selected job or building.", + job_material, + job_material_hotkey)); } if (ui_workshop_job_cursor && job_next_id) { commands.push_back( PluginCommand( - "job-duplicate", "Duplicate the selected job in a workshop.", - job_duplicate, Gui::workshop_job_hotkey, - " - In 'q' mode, when a job is highlighted within a workshop\n" - " or furnace building, instantly duplicates the job.\n" - ) - ); + "job-duplicate", + "Duplicate the selected job in a workshop.", + job_duplicate, + Gui::workshop_job_hotkey)); } return CR_OK; From 382ad84125195b70ed86500fb3fd99bfbd707dce Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 27 Jul 2022 16:14:24 -0700 Subject: [PATCH 353/854] add example for job item-type --- docs/plugins/jobutils.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/plugins/jobutils.rst b/docs/plugins/jobutils.rst index 8863c7a9b..e8434abdc 100644 --- a/docs/plugins/jobutils.rst +++ b/docs/plugins/jobutils.rst @@ -43,3 +43,7 @@ Examples Change the selected "Construct Traction Bench" job (which has three source items: a table, a mechanism, and a chain) to specifically use a marble mechanism. +``job item-type 2 TABLE`` + Change the selected "Encrust furniture with blue jade" job (which has two + source items: a cut gem and a piece of improvable furniture) to specifically + use a table instead of just any furniture. From ed113466d92e298a8d53a70c47dbfa3e645f5cc0 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 27 Jul 2022 16:29:54 -0700 Subject: [PATCH 354/854] update docs for isoworldremote --- docs/plugins/isoworldremote.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/plugins/isoworldremote.rst b/docs/plugins/isoworldremote.rst index 7bacc2b57..2c359fac2 100644 --- a/docs/plugins/isoworldremote.rst +++ b/docs/plugins/isoworldremote.rst @@ -1,3 +1,5 @@ isoworldremote ============== -A plugin that implements a `remote API ` used by Isoworld. +Tags: + +Provides a `remote API ` used by Isoworld. From a053cce444893c94f84269350f71766d268e0991 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 27 Jul 2022 16:30:14 -0700 Subject: [PATCH 355/854] update the "requires interactive terminal" message to make it more user friendly --- library/Core.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index facd02823..1b3f1409f 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1235,7 +1235,8 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } } else if (res == CR_NEEDS_CONSOLE) - con.printerr("%s needs interactive console to work.\n", first.c_str()); + con.printerr("%s needs an interactive console to work.\n" + "Please run this command from the DFHack terminal.\n", first.c_str()); return res; } From 58c0f94a2d4f445370016f58d5932724b9f8f908 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 27 Jul 2022 17:14:00 -0700 Subject: [PATCH 356/854] order autolabor docs similarly to labormanager --- docs/plugins/autolabor.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/plugins/autolabor.rst b/docs/plugins/autolabor.rst index ca249ee19..dc14a40bb 100644 --- a/docs/plugins/autolabor.rst +++ b/docs/plugins/autolabor.rst @@ -70,6 +70,11 @@ Examples Advanced usage -------------- +``autolabor list`` + List current status of all labors. Use this command to see the IDs for all + labors. +``autolabor status`` + Show basic status information. ``autolabor [] []`` Set range of dwarves assigned to a labor, optionally specifying the size of the pool of most skilled dwarves that will ever be considered for this @@ -80,9 +85,5 @@ Advanced usage Turn off autolabor for a specific labor. ``autolabor reset-all| reset`` Return a labor (or all labors) to the default handling. -``autolabor list`` - List current status of all labors. -``autolabor status`` - Show basic status information. See `autolabor-artisans` for a differently-tuned setup. From e94f1891c26d457f37ce98e13e2f4fe79eb03d6c Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 27 Jul 2022 17:14:18 -0700 Subject: [PATCH 357/854] update docs for labormanager --- docs/plugins/labormanager.rst | 216 ++++++++++++++------------ plugins/labormanager/labormanager.cpp | 35 +---- 2 files changed, 122 insertions(+), 129 deletions(-) diff --git a/docs/plugins/labormanager.rst b/docs/plugins/labormanager.rst index d16a10e33..103ecde2f 100644 --- a/docs/plugins/labormanager.rst +++ b/docs/plugins/labormanager.rst @@ -1,105 +1,127 @@ labormanager ============ -Automatically manage dwarf labors to efficiently complete jobs. -Labormanager is derived from autolabor (above) but uses a completely -different approach to assigning jobs to dwarves. While autolabor tries -to keep as many dwarves busy as possible, labormanager instead strives -to get jobs done as quickly as possible. +Tags: +:dfhack-keybind:`labormanager` -Labormanager frequently scans the current job list, current list of -dwarfs, and the map to determine how many dwarves need to be assigned to -what labors in order to meet all current labor needs without starving -any particular type of job. +Automatically manage dwarf labors. Labormanager is derived from `autolabor` +but uses a completely different approach to assigning jobs to dwarves. While +autolabor tries to keep as many dwarves busy as possible, labormanager instead +strives to get jobs done as quickly as possible. + +Labormanager frequently scans the current job list, current list of dwarves, and +the map to determine how many dwarves need to be assigned to what labors in +order to meet all current labor needs without starving any particular type of +job. + +Dwarves on active military duty or dwarves assigned to burrows are left +untouched. .. warning:: - *As with autolabor, labormanager will override any manual changes you - make to labors while it is enabled, including through other tools such - as Dwarf Therapist* - -Simple usage: - -:enable labormanager: Enables the plugin with default settings. - (Persistent per fortress) - -:disable labormanager: Disables the plugin. - -Anything beyond this is optional - labormanager works fairly well on the -default settings. - -The default priorities for each labor vary (some labors are higher -priority by default than others). The way the plugin works is that, once -it determines how many of each labor is needed, it then sorts them by -adjusted priority. (Labors other than hauling have a bias added to them -based on how long it's been since they were last used, to prevent job -starvation.) The labor with the highest priority is selected, the "best -fit" dwarf for that labor is assigned to that labor, and then its -priority is *halved*. This process is repeated until either dwarfs or -labors run out. - -Because there is no easy way to detect how many haulers are actually -needed at any moment, the plugin always ensures that at least one dwarf -is assigned to each of the hauling labors, even if no hauling jobs are -detected. At least one dwarf is always assigned to construction removing -and cleaning because these jobs also cannot be easily detected. Lever -pulling is always assigned to everyone. Any dwarfs for which there are -no jobs will be assigned hauling, lever pulling, and cleaning labors. If -you use animal trainers, note that labormanager will misbehave if you -assign specific trainers to specific animals; results are only guaranteed -if you use "any trainer", and animal trainers will probably be -overallocated in any case. - -Labormanager also sometimes assigns extra labors to currently busy -dwarfs so that when they finish their current job, they will go off and -do something useful instead of standing around waiting for a job. - -There is special handling to ensure that at least one dwarf is assigned -to haul food whenever food is detected left in a place where it will rot -if not stored. This will cause a dwarf to go idle if you have no -storepiles to haul food to. - -Dwarfs who are unable to work (child, in the military, wounded, -handless, asleep, in a meeting) are entirely excluded from labor -assignment. Any dwarf explicitly assigned to a burrow will also be -completely ignored by labormanager. - -The fitness algorithm for assigning jobs to dwarfs generally attempts to -favor dwarfs who are more skilled over those who are less skilled. It -also tries to avoid assigning female dwarfs with children to jobs that -are "outside", favors assigning "outside" jobs to dwarfs who are -carrying a tool that could be used as a weapon, and tries to minimize -how often dwarfs have to reequip. - -Labormanager automatically determines medical needs and reserves health -care providers as needed. Note that this may cause idling if you have -injured dwarfs but no or inadequate hospital facilities. - -Hunting is never assigned without a butchery, and fishing is never -assigned without a fishery, and neither of these labors is assigned -unless specifically enabled. + As with autolabor, labormanager will override any manual changes you make to + labors while it is enabled, including through other tools such as Dwarf + Therapist. Do not run both autolabor and labormanager at the same time! + +Usage:: + + enable labormanager + +Anything beyond this is optional - labormanager works well with the default +settings. Once you have enabled it in a fortress, it stays enabled until you +explicitly disable it, even if you save and reload your game. + +The default priorities for each labor vary (some labors are higher priority by +default than others). The way the plugin works is that, once it determines how +many jobs of each labor is needed, it then sorts them by adjusted priority. +(Labors other than hauling have a bias added to them based on how long it's been +since they were last used to prevent job starvation.) The labor with the highest +priority is selected, the "best fit" dwarf for that labor is assigned to that +labor, and then its priority is *halved*. This process is repeated until either +dwarves or labors run out. + +Because there is no easy way to detect how many haulers are actually needed at +any moment, the plugin always ensures that at least one dwarf is assigned to +each of the hauling labors, even if no hauling jobs are detected. At least one +dwarf is always assigned to construction removing and cleaning because these +jobs also cannot be easily detected. Lever pulling is always assigned to +everyone. Any dwarves for which there are no jobs will be assigned hauling, +lever pulling, and cleaning labors. If you use animal trainers, note that +labormanager will misbehave if you assign specific trainers to specific animals; +results are only guaranteed if you use "any trainer". + +Labormanager also sometimes assigns extra labors to currently busy dwarfs so +that when they finish their current job, they will go off and do something +useful instead of standing around waiting for a job. + +There is special handling to ensure that at least one dwarf is assigned to haul +food whenever food is detected left in a place where it will rot if not stored. +This will cause a dwarf to go idle if you have no stockpiles to haul food to. + +Dwarves who are unable to work (child, in the military, wounded, handless, +asleep, in a meeting) are entirely excluded from labor assignment. Any dwarf +explicitly assigned to a burrow will also be completely ignored by labormanager. + +The fitness algorithm for assigning jobs to dwarves generally attempts to favor +dwarves who are more skilled over those who are less skilled. It also tries to +avoid assigning female dwarfs with children to jobs that are "outside", favors +assigning "outside" jobs to dwarfs who are carrying a tool that could be used as +a weapon, and tries to minimize how often dwarves have to reequip. + +Labormanager automatically determines medical needs and reserves health care +providers as needed. Note that this may cause idling if you have injured dwarves +but no or inadequate hospital facilities. + +Hunting is never assigned without a butchery, and fishing is never assigned +without a fishery, and neither of these labors is assigned unless specifically +enabled (see below). The method by which labormanager determines what labor is needed for a -particular job is complicated and, in places, incomplete. In some -situations, labormanager will detect that it cannot determine what labor -is required. It will, by default, pause and print an error message on -the dfhack console, followed by the message "LABORMANAGER: Game paused -so you can investigate the above message.". If this happens, please open -an issue on github, reporting the lines that immediately preceded this -message. You can tell labormanager to ignore this error and carry on by -typing ``labormanager pause-on-error no``, but be warned that some job may go -undone in this situation. - -Advanced usage: - -:labormanager enable: Turn plugin on. -:labormanager disable: Turn plugin off. -:labormanager priority : Set the priority value (see above) for labor to . -:labormanager reset : Reset the priority value of labor to its default. -:labormanager reset-all: Reset all priority values to their defaults. -:labormanager allow-fishing: Allow dwarfs to fish. *Warning* This tends to result in most of the fort going fishing. -:labormanager forbid-fishing: Forbid dwarfs from fishing. Default behavior. -:labormanager allow-hunting: Allow dwarfs to hunt. *Warning* This tends to result in as many dwarfs going hunting as you have crossbows. -:labormanager forbid-hunting: Forbid dwarfs from hunting. Default behavior. -:labormanager list: Show current priorities and current allocation stats. -:labormanager pause-on-error yes: Make labormanager pause if the labor inference engine fails. See above. -:labormanager pause-on-error no: Allow labormanager to continue past a labor inference engine failure. +particular job is complicated and, in places, incomplete. In some situations, +labormanager will detect that it cannot determine what labor is required. It +will, by default, pause and print an error message on the dfhack console, +followed by the message "LABORMANAGER: Game paused so you can investigate the +above message.". If this happens, please open an :issue:`` on GitHub, +reporting the lines that immediately preceded this message. You can tell +labormanager to ignore this error and carry on by running +``labormanager pause-on-error no``, but be warned that some job may go undone in +this situation. + +Examples +-------- + +``labormanager priority BREWER 500`` + Boost the priority of brewing jobs. +``labormanager max FISH 1`` + Only assign fishing to one dwarf at a time. Note that you also have to run + ``labormanager allow-fishing`` for any dwarves to be assigned fishing at + all. + +Advanced usage +-------------- + +``labormanager list`` + Show current priorities and current allocation stats. Use this command to + see the IDs for all labors. +``labormanager status`` + Show basic status information. +``labormanager priority `` + Set the priority value for labor to . +``labormanager max `` + Set maximum number of dwarves that can be assigned to a labor. +``labormanager max none`` + Unrestrict the number of dwarves that can be assigned to a labor. +``labormanager max disable`` + Don't manage the specified labor. Dwarves who you have manually enabled this + labor on will be less likely to have managed labors assigned to them. +``labormanager reset-all|reset `` + Return a labor (or all labors) to the default priority. +``labormanager allow-fishing|forbid-fishing`` + Allow/disallow fisherdwarves. *Warning* Allowing fishing tends to result in + most of the fort going fishing. Fishing is forbidden by default. +``labormanager allow-hunting|forbid-hunting`` + Allow/disallow hunterdwarves. *Warning* Allowing hunting tends to result in + as many dwarves going hunting as you have crossbows. Hunting is forbidden by + default. +``labormanager pause-on-error yes|no`` + Make labormanager pause/continue if the labor inference engine fails. See + the above section for details. diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 02569fd8b..f508c9797 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -845,38 +845,9 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector \n" - " Set max number of dwarves assigned to a labor.\n" - " labormanager max unmanaged\n" - " labormanager max disable\n" - " Don't attempt to manage this labor.\n" - " Any dwarves with unmanaged labors assigned will be less\n" - " likely to have managed labors assigned to them.\n" - " labormanager max none\n" - " Unrestrict the number of dwarves assigned to a labor.\n" - " labormanager priority \n" - " Change the assignment priority of a labor (default is 100)\n" - " labormanager reset \n" - " Return a labor to the default handling.\n" - " labormanager reset-all\n" - " Return all labors to the default handling.\n" - " labormanager list\n" - " List current status of all labors.\n" - " labormanager status\n" - " Show basic status information.\n" - "Function:\n" - " When enabled, labormanager periodically checks your dwarves and enables or\n" - " disables labors. Generally, each dwarf will be assigned exactly one labor.\n" - " Warning: labormanager will override any manual changes you make to labors\n" - " while it is enabled, except where the labor is marked as unmanaged.\n" - " Do not try to run both autolabor and labormanager at the same time.\n" - )); + "labormanager", + "Automatically manage dwarf labors.", + labormanager)); generate_labor_to_skill_map(); From 52a0b0f2ca8758dc792e5402f0393f75d8e4b9c7 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 27 Jul 2022 17:14:30 -0700 Subject: [PATCH 358/854] update docs for lair --- docs/plugins/lair.rst | 19 ++++++++++++------- plugins/lair.cpp | 6 ++++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/docs/plugins/lair.rst b/docs/plugins/lair.rst index b80e71e3a..b4a7ed524 100644 --- a/docs/plugins/lair.rst +++ b/docs/plugins/lair.rst @@ -1,12 +1,17 @@ lair ==== -This command allows you to mark the map as a monster lair, preventing item -scatter on abandon. When invoked as ``lair reset``, it does the opposite. +Tags: +:dfhack-keybind:`lair` -Unlike `reveal`, this command doesn't save the information about tiles - you -won't be able to restore state of real monster lairs using ``lair reset``. +Mark the map as a monster lair. This avoids item scatter when the fortress is +abandoned. -Options: +Usage: -:lair: Mark the map as monster lair -:lair reset: Mark the map as ordinary (not lair) +``lair`` + Mark the map as monster lair. +``lair reset`` + Mark the map as ordinary (not lair). + +This command doesn't save the information about tiles - you won't be able to +restore the state of a real monster lairs using ``lair reset``. diff --git a/plugins/lair.cpp b/plugins/lair.cpp index 6fb167988..7c3b1953f 100644 --- a/plugins/lair.cpp +++ b/plugins/lair.cpp @@ -61,7 +61,9 @@ command_result lair(color_ostream &out, std::vector & params) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("lair","Mark the map as a monster lair (avoids item scatter)",lair, false, - "Usage: 'lair' to mark entire map as monster lair, 'lair reset' to undo the operation.\n")); + commands.push_back(PluginCommand( + "lair", + "Mark the map as a monster lair to avoid item scatter.", + lair)); return CR_OK; } From ddae1aa90050a2917f6af008ad19470d324461cc Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 27 Jul 2022 17:14:40 -0700 Subject: [PATCH 359/854] update docs for liquids --- docs/plugins/liquids.rst | 51 ++++++++++++++++++++++------------------ plugins/liquids.cpp | 18 +++++++------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/docs/plugins/liquids.rst b/docs/plugins/liquids.rst index 75e41da5b..61e4bcfe2 100644 --- a/docs/plugins/liquids.rst +++ b/docs/plugins/liquids.rst @@ -2,30 +2,42 @@ liquids ======= -Allows adding magma, water and obsidian to the game. It replaces the normal -dfhack command line and can't be used from a hotkey. Settings will be remembered -as long as dfhack runs. Intended for use in combination with the command -``liquids-here`` (which can be bound to a hotkey). See also :issue:`80`. +Tags: +:dfhack-keybind:`liquids` +:dfhack-keybind:`liquids-here` -.. warning:: +Place magma, water or obsidian. See `gui/liquids` for an in-game interface for +this functionality. + +Also, if you only want to add or remove water or magma from a single tile, the +`source` script may be easier to use. + +Usage: - Spawning and deleting liquids can mess up pathing data and - temperatures (creating heat traps). You've been warned. +``liquids`` + Start the interactive terminal settings interpreter. This command must be + called from the DFHack terminal and not from any in-game interface. +``liquids-here`` + Run the liquid spawner with the current/last settings made in ``liquids`` + (if no settings in ``liquids`` were made, then it paints a point of 7/7 + magma by default). This command is intended to be used as keybinding, and it + requires an active in-game cursor. -.. note:: +.. warning:: - `gui/liquids` is an in-game UI for this script. + Spawning and deleting liquids can mess up pathing data and temperatures + (creating heat traps). You've been warned. -Settings will be remembered until you quit DF. You can call `liquids-here` to execute -the last configured action, which is useful in combination with keybindings. +Interactive interpreter +----------------------- -Usage: point the DF cursor at a tile you want to modify and use the commands. +The interpreter replaces the normal dfhack command line and can't be used from a +hotkey. Settings will be remembered as long as dfhack runs. It is intended for +use in combination with the command ``liquids-here`` (which *can* be bound to a +hotkey). -If you only want to add or remove water or magma from one tile, -`source` may be easier to use. +You can enter the following commands at the prompt. -Commands --------- Misc commands: :q: quit @@ -65,10 +77,3 @@ Brush size and shape: :block: DF map block with cursor in it (regular spaced 16x16x1 blocks) :column: Column from cursor, up through free space :flood: Flood-fill water tiles from cursor (only makes sense with wclean) - -liquids-here ------------- -Run the liquid spawner with the current/last settings made in liquids (if no -settings in liquids were made it paints a point of 7/7 magma by default). - -Intended to be used as keybinding. Requires an active in-game cursor. diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 76c098c8a..980deb747 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -65,17 +65,15 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Wed, 27 Jul 2022 22:03:03 -0400 Subject: [PATCH 360/854] docs/build.py: Add support for xml and pseudoxml output formats Useful for debugging layout of generated docutils nodes --- docs/build.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/build.py b/docs/build.py index 2a42d0968..582279bcb 100755 --- a/docs/build.py +++ b/docs/build.py @@ -28,6 +28,8 @@ OUTPUT_FORMATS = { 'html': SphinxOutputFormat('html', pre_args=['-b', 'html']), 'text': SphinxOutputFormat('text', pre_args=['-b', 'text']), 'pdf': SphinxOutputFormat('pdf', pre_args=['-M', 'latexpdf']), + 'xml': SphinxOutputFormat('xml', pre_args=['-b', 'xml']), + 'pseudoxml': SphinxOutputFormat('pseudoxml', pre_args=['-b', 'pseudoxml']), } def _parse_known_args(parser, source_args): From 6aabf5f3c3ec84551fda0cb5118066de1c33a375 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 27 Jul 2022 17:21:34 -0700 Subject: [PATCH 361/854] no commas in index params --- docs/plugins/command-prompt.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins/command-prompt.rst b/docs/plugins/command-prompt.rst index 3b46e1b33..8e6c9e885 100644 --- a/docs/plugins/command-prompt.rst +++ b/docs/plugins/command-prompt.rst @@ -3,8 +3,8 @@ command-prompt Tags: :dfhack-keybind:`command-prompt` -:index:`An in-game DFHack terminal, where you can enter other commands. -` +:index:`An in-game DFHack terminal where you can enter other commands. +` Usage:: From 756900393f35a415af124da075e9cf2e2221a783 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 02:46:48 -0700 Subject: [PATCH 362/854] update docs for misery --- docs/plugins/misery.rst | 22 +++++++++++++--------- plugins/misery.cpp | 19 ++++--------------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/docs/plugins/misery.rst b/docs/plugins/misery.rst index 1c146a10f..9a3a632f2 100644 --- a/docs/plugins/misery.rst +++ b/docs/plugins/misery.rst @@ -1,14 +1,18 @@ misery ====== -When enabled, fake bad thoughts will be added to all dwarves. +Tags: +:dfhack-keybind:`misery` + +Increase the intensity of negative dwarven thoughts. + +When enabled, negative thoughts that your dwarves have will multiply by the +specified factor. Usage: -:misery enable n: enable misery with optional magnitude n. If specified, n must - be positive. -:misery n: same as "misery enable n" -:misery enable: same as "misery enable 1" -:misery disable: stop adding new negative thoughts. This will not remove - existing negative thoughts. Equivalent to "misery 0". -:misery clear: remove fake thoughts, even after saving and reloading. Does - not change factor. +``enable misery`` + Start multiplying negative thoughts. +``misery `` + Change the multiplicative factor of bad thoughts. The default is ``2``. +``misery clear`` + Clear away negative thoughts added by ``misery``. diff --git a/plugins/misery.cpp b/plugins/misery.cpp index b8e11f5c0..224060c6b 100644 --- a/plugins/misery.cpp +++ b/plugins/misery.cpp @@ -128,21 +128,10 @@ DFhackCExport command_result plugin_onupdate(color_ostream& out) { } DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { - commands.push_back(PluginCommand("misery", "increase the intensity of negative dwarven thoughts", - &misery, false, - "misery: When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).\n" - "Usage:\n" - " misery enable n\n" - " enable misery with optional magnitude n. If specified, n must be positive.\n" - " misery n\n" - " same as \"misery enable n\"\n" - " misery enable\n" - " same as \"misery enable 2\"\n" - " misery disable\n" - " stop adding new negative thoughts. This will not remove existing duplicated thoughts. Equivalent to \"misery 1\"\n" - " misery clear\n" - " remove fake thoughts added in this session of DF. Saving makes them permanent! Does not change factor.\n\n" - )); + commands.push_back(PluginCommand( + "misery", + "Increase the intensity of negative dwarven thoughts.", + misery)); return CR_OK; } From b6f20ee61a543423e7f2906c44d99b01dd5b15fe Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 02:49:46 -0700 Subject: [PATCH 363/854] update docs for manipulator --- docs/plugins/manipulator.rst | 43 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/docs/plugins/manipulator.rst b/docs/plugins/manipulator.rst index 024438d85..305499308 100644 --- a/docs/plugins/manipulator.rst +++ b/docs/plugins/manipulator.rst @@ -1,13 +1,20 @@ manipulator =========== -An in-game equivalent to the popular program Dwarf Therapist. +Tags: + +An in-game labor management interface. It is equivalent to the popular Dwarf +Therapist utility. To activate, open the unit screen and press :kbd:`l`. +Usage:: + + enable manipulator + .. image:: ../images/manipulator.png -The far left column displays the unit's Happiness (color-coded based on its -value), Name, Profession/Squad, and the right half of the screen displays each +The far left column displays the unit's name, happiness (color-coded based on +its value), profession, or squad, and the right half of the screen displays each dwarf's labor settings and skill levels (0-9 for Dabbling through Professional, A-E for Great through Grand Master, and U-Z for Legendary through Legendary+5). @@ -58,12 +65,14 @@ The manipulator plugin supports saving professions: a named set of labors that c quickly applied to one or multiple dwarves. To save a profession, highlight a dwarf and press :kbd:`P`. The profession will be saved using -the custom profession name of the dwarf, or the default for that dwarf if no custom profession -name has been set. +the custom profession name of the dwarf, or the default profession name for that dwarf if no +custom profession name has been set. To apply a profession, either highlight a single dwarf or select multiple with :kbd:`x`, and press :kbd:`p` to select the profession to apply. All labors for -the selected dwarves will be reset to the labors of the chosen profession. +the selected dwarves will be reset to the labors of the chosen profession and +the custom profession names for those dwarves will be set to the applied +profession. Professions are saved as human-readable text files in the ``dfhack-config/professions`` folder within the DF folder, and can be edited or @@ -96,13 +105,8 @@ Chef 0 3 Buchery, Tanning, and Cooking. It is important to Craftsdwarf 0 4-6 All labors used at Craftsdwarf's workshops, Glassmaker's workshops, and kilns. Doctor 0 2-4 The full suite of medical labors, plus Animal - Caretaking for those using the dwarfvet plugin. -Farmer 1 4 Food- and animal product-related labors. This - profession also has the ``Alchemist`` labor - enabled since they need to focus on food-related - jobs, though you might want to disable - ``Alchemist`` for your first farmer until there - are actual farming duties to perform. + Caretaking for those using the `dwarfvet` plugin. +Farmer 1 4 Food- and animal product-related labors. Fisherdwarf 0 0-1 Fishing and fish cleaning. If you assign this profession to any dwarf, be prepared to be inundated with fish. Fisherdwarves *never stop @@ -117,24 +121,21 @@ Hauler 0 >20 All hauling labors plus Siege Operating, Mechanic hauling labors for other dwarves so they can focus on their skilled tasks. You may also want to restrict your Mechanic's workshops to only - skilled mechanics so your haulers don't make - low-quality mechanisms. + skilled mechanics so your unskilled haulers don't + make low-quality mechanisms. Laborer 0 10-12 All labors that don't improve quality with skill, such as Soapmaking and furnace labors. Marksdwarf 0 10-30 Similar to Hauler. See the description for Meleedwarf below for more details. -Mason 2 2-4 Masonry and Gem Cutting/Encrusting. In the early - game, you may need to run "`prioritize` - ConstructBuilding" to get your masons to build - wells and bridges if they are too busy crafting - stone furniture. +Mason 2 2-4 Masonry and Gem Cutting/Encrusting. Meleedwarf 0 20-50 Similar to Hauler, but without most civilian labors. This profession is separate from Hauler so you can find your military dwarves easily. Meleedwarves and Marksdwarves have Mechanics and hauling labors enabled so you can temporarily deactivate your military after sieges and allow - your military dwarves to help clean up. + your military dwarves to help clean up and reset + traps. Migrant 0 0 You can assign this profession to new migrants temporarily while you sort them into professions. Like Marksdwarf and Meleedwarf, the purpose of From bcab483b57dce9f50846910e4af80ce454f7cf32 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 02:50:37 -0700 Subject: [PATCH 364/854] update docs for mode --- docs/plugins/mode.rst | 33 +++++++++++++++++++++++++-------- plugins/mode.cpp | 10 ++++------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/docs/plugins/mode.rst b/docs/plugins/mode.rst index 9b3fc6984..225a49e1e 100644 --- a/docs/plugins/mode.rst +++ b/docs/plugins/mode.rst @@ -1,5 +1,8 @@ mode ==== +Tags: +:dfhack-keybind:`mode` + This command lets you see and change the game mode directly. .. warning:: @@ -9,13 +12,27 @@ This command lets you see and change the game mode directly. Not all combinations are good for every situation and most of them will produce undesirable results. There are a few good ones though. -Examples: +Usage: + +``mode`` + Print the current game mode. +``mode set`` + Enter an interactive commandline menu where you can set the game mode. + +Examples +-------- + +Scenario 1: + +* You are in fort game mode, managing your fortress and paused. +* You switch to the arena game mode, *assume control of a creature* and then +* switch to adventure game mode. + +You just lost a fortress and gained an adventurer. + +Scenario 2: - * You are in fort game mode, managing your fortress and paused. - * You switch to the arena game mode, *assume control of a creature* and then - * switch to adventure game mode(1). - You just lost a fortress and gained an adventurer. Alternatively: +* You are in fort game mode, managing your fortress and paused at the Esc menu. +* You switch to the adventure game mode, assume control of a creature, then save or retire. - * You are in fort game mode, managing your fortress and paused at the esc menu. - * You switch to the adventure game mode, assume control of a creature, then save or retire. - * You just created a returnable mountain home and gained an adventurer. +You just created a returnable mountain home and gained an adventurer. diff --git a/plugins/mode.cpp b/plugins/mode.cpp index 5e58c50a0..aeda6380b 100644 --- a/plugins/mode.cpp +++ b/plugins/mode.cpp @@ -19,12 +19,10 @@ DFHACK_PLUGIN("mode"); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "mode","View, change and track game mode.", - mode, true, - " Without any parameters, this command prints the current game mode\n" - " You can interactively set the game mode with 'mode set'.\n" - "!!Setting the game modes can be dangerous and break your game!!\n" - )); + "mode", + "View, change and track game mode.", + mode, + true)); return CR_OK; } From f68b58c46047396cc2132618d0b0729f752d3ae3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 03:22:32 -0700 Subject: [PATCH 365/854] update docs for mousequery --- docs/Tags.rst | 35 ++++++++++++++++---------------- docs/plugins/mousequery.rst | 40 +++++++++++++++++++++++++++---------- plugins/mousequery.cpp | 16 +++------------ 3 files changed, 51 insertions(+), 40 deletions(-) diff --git a/docs/Tags.rst b/docs/Tags.rst index 432632ba1..294358bd8 100644 --- a/docs/Tags.rst +++ b/docs/Tags.rst @@ -1,19 +1,20 @@ :orphan: -- `tag/adventure`: Tools that are useful while in adventure mode -- `tag/fort`: Tools that are useful while in fort mode -- `tag/legends`: Tools that are useful while in legends mode -- `tag/items`: Tools that create or modify in-game items -- `tag/units`: Tools that create or modify units -- `tag/jobs`: Tools that create or modify jobs -- `tag/labors`: Tools that deal with labor assignment -- `tag/auto`: Tools that automatically manage some aspect of your fortress -- `tag/map`: Map modification -- `tag/system`: Tools related to working with DFHack commands or the core DFHack library -- `tag/productivity`: Tools that help you do things that you could do manually, but using the tool is better and faster -- `tag/animals`: Tools that help you manage animals -- `tag/fix`: Tools that fix specific bugs -- `tag/inspection`: Tools that let you inspect game data -- `tag/buildings`: Tools that help you work wtih placing or configuring buildings and furniture -- `tag/quickfort`: Tools that are involved in creating and playing back blueprints -- `tag/dev`: Tools useful for develpers and modders +- `tag/adventure`: Tools that are useful while in adventure mode. +- `tag/fort`: Tools that are useful while in fort mode. +- `tag/legends`: Tools that are useful while in legends mode. +- `tag/items`: Tools that create or modify in-game items. +- `tag/units`: Tools that create or modify units. +- `tag/jobs`: Tools that create or modify jobs. +- `tag/labors`: Tools that deal with labor assignment. +- `tag/auto`: Tools that automatically manage some aspect of your fortress. +- `tag/map`: Map modification. +- `tag/system`: Tools related to working with DFHack commands or the core DFHack library. +- `tag/productivity`: Tools that help you do things that you could do manually, but using the tool is better and faster. +- `tag/animals`: Tools that help you manage animals. +- `tag/fix`: Tools that fix specific bugs. +- `tag/inspection`: Tools that let you inspect game data. +- `tag/buildings`: Tools that help you work wtih placing or configuring buildings and furniture. +- `tag/quickfort`: Tools that are involved in creating and playing back blueprints. +- `tag/dev`: Tools useful for develpers and modders. +- `tag/interface`: Tools that modify or extend the user interface. diff --git a/docs/plugins/mousequery.rst b/docs/plugins/mousequery.rst index 2f613acaa..eedca7646 100644 --- a/docs/plugins/mousequery.rst +++ b/docs/plugins/mousequery.rst @@ -1,16 +1,36 @@ mousequery ========== -Adds mouse controls to the DF interface, e.g. click-and-drag designations. +Tags: `tag/fort`, `tag/interface` +:dfhack-keybind:`mousequery` -Options: - -:plugin: enable/disable the entire plugin -:rbutton: enable/disable right mouse button -:track: enable/disable moving cursor in build and designation mode -:edge: enable/disable active edge scrolling (when on, will also enable tracking) -:live: enable/disable query view when unpaused -:delay: Set delay when edge scrolling in tracking mode. Omit amount to display current setting. +Adds mouse controls to the DF interface. For example, with ``mousequery`` you +can click on buildings to configure them, hold the mouse button to draw dig +designations, or click and drag to move the map around. Usage:: - mousequery [plugin] [rbutton] [track] [edge] [live] [enable|disable] + enable mousequery + mousequery [rbutton|track|edge|live] [enable|disable] + mousequery drag [left|right|disable] + mousequery delay [] + +:rbutton: When the right mouse button is clicked, cancel out of menus or + scroll the main map if you r-click near an edge. +:track: Move the cursor with the mouse instead of the cursor keys when you + are in build or designation modes. +:edge: Scroll the map when you move the cursor to a map edge. See ``delay`` + below. If enabled also enables ``track``. +:delay: Set delay in milliseconds for map edge scrolling. Omit the amount to + display the current setting. +:live: Display information in the lower right corner of the screen about + the items/building/tile under the cursor, even while unpaused. + +Examples +-------- + +``mousequery rbutton enable`` + Enable using the right mouse button to cancel out of menus and scroll the + map. +``mousequery edge enable`` +``mousequery delay 300`` + Enable edge scrolling and set the delay to 300ms. diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 618252ad3..cd85de2bc 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -916,19 +916,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector \n" - " Set delay when edge scrolling in tracking mode. Omit amount to display current setting.\n" - )); + "mousequery", + "Add mouse functionality to Dwarf Fortress.", + mousequery_cmd)); return CR_OK; } From 88648284b0d72a2c25086c4fcd1a0d5486179a04 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 03:25:00 -0700 Subject: [PATCH 366/854] update docs for nestboxes --- docs/plugins/nestboxes.rst | 11 +++++++++-- plugins/nestboxes.cpp | 11 ++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/plugins/nestboxes.rst b/docs/plugins/nestboxes.rst index 2b633f31b..fb9df20a9 100644 --- a/docs/plugins/nestboxes.rst +++ b/docs/plugins/nestboxes.rst @@ -1,5 +1,12 @@ nestboxes ========= +Tags: -Automatically scan for and forbid fertile eggs incubating in a nestbox. -Toggle status with `enable` or `disable `. +Protect fertile eggs incubating in a nestbox. This plugin will automatically +scan for and forbid fertile eggs incubating in a nestbox so that dwarves won't +come to collect them for eating. The eggs will hatch normally, even when +forbidden. + +Usage:: + + enable nestboxes diff --git a/plugins/nestboxes.cpp b/plugins/nestboxes.cpp index 8b9a33289..403a8cb37 100644 --- a/plugins/nestboxes.cpp +++ b/plugins/nestboxes.cpp @@ -69,13 +69,10 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector Date: Thu, 28 Jul 2022 03:48:27 -0700 Subject: [PATCH 367/854] update docs for orders --- docs/plugins/orders.rst | 95 +++++++++++++++++++++++++---------------- plugins/orders.cpp | 17 +------- 2 files changed, 61 insertions(+), 51 deletions(-) diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index eb0e8a7ca..2506bc582 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -1,31 +1,51 @@ orders ====== - -A plugin for manipulating manager orders. - -Subcommands: - -:list: Shows the list of previously exported orders, including the orders library. -:export NAME: Exports the current list of manager orders to a file named ``dfhack-config/orders/NAME.json``. -:import NAME: Imports manager orders from a file named ``dfhack-config/orders/NAME.json``. -:clear: Deletes all manager orders in the current embark. -:sort: Sorts current manager orders by repeat frequency so daily orders don't - prevent other orders from ever being completed: one-time orders first, then - yearly, seasonally, monthly, then finally daily. +Tags: +:dfhack-keybind:`orders` + +Manage manager orders. + +Usage: + +``orders orders list`` + Shows the list of previously exported orders, including the orders library. +``orders export `` + Saves all the current manager orders in a file. +``orders import `` + Imports the specified manager orders. Note this adds to your current set of + manager orders. It will not clear the orders that already exist. +``orders clear`` + Deletes all manager orders in the current embark. +``orders sort`` + Sorts current manager orders by repeat frequency so repeating orders don't + prevent one-time orders from ever being completed. The sorting order is: + one-time orders first, then yearly, seasonally, monthly, and finally, daily. You can keep your orders automatically sorted by adding the following command to your ``onMapLoad.init`` file:: repeat -name orders-sort -time 1 -timeUnits days -command [ orders sort ] +Exported orders are saved in the ``dfhack-config/orders`` directory, where you +can view, edit, and delete them, if desired. + +Examples +-------- + +``orders export myorders`` + Export the current manager orders to a file named + ``dfhack-config/orders/myorders.json``. +``orders import library/basic`` + Import manager orders from the library that keep your fort stocked with + basic essentials. The orders library ------------------ DFHack comes with a library of useful manager orders that are ready for import: -:source:`basic.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:source:`library/basic ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This collection of orders handles basic fort necessities: @@ -45,12 +65,12 @@ This collection of orders handles basic fort necessities: You should import it as soon as you have enough dwarves to perform the tasks. Right after the first migration wave is usually a good time. -:source:`furnace.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:source:`library/furnace ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This collection creates basic items that require heat. It is separated out from -``basic.json`` to give players the opportunity to set up magma furnaces first in -order to save resources. It handles: +``library/basic`` to give players the opportunity to set up magma furnaces first +in order to save resources. It handles: - charcoal (including smelting of bituminous coal and lignite) - pearlash @@ -61,8 +81,8 @@ order to save resources. It handles: Orders are missing for plaster powder until DF :bug:`11803` is fixed. -:source:`military.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:source:`library/military ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This collection adds high-volume smelting jobs for military-grade metal ores and produces weapons and armor: @@ -83,33 +103,36 @@ Make sure you have a lot of fuel (or magma forges and furnaces) before you turn This file should only be imported, of course, if you need to equip a military. -:source:`smelting.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:source:`library/smelting ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This collection adds smelting jobs for all ores. It includes handling the ores -already managed by ``military.json``, but has lower limits. This ensures all -ores will be covered if a player imports ``smelting`` but not ``military``, but -the higher-volume ``military`` orders will take priority if both are imported. +already managed by ``library/military``, but has lower limits. This ensures all +ores will be covered if a player imports ``library/smelting`` but not +``library/military``, but the higher-volume ``library/military`` orders will +take priority if both are imported. -:source:`rockstock.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:source:`library/rockstock ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This collection of orders keeps a small stock of all types of rock furniture. This allows you to do ad-hoc furnishings of guildhalls, libraries, temples, or other rooms with `buildingplan` and your masons will make sure there is always stock on hand to fulfill the plans. -:source:`glassstock.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:source:`library/glassstock ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Similar to ``rockstock`` above, this collection keeps a small stock of all types -of glass furniture. If you have a functioning glass industry, this is more -sustainable than ``rockstock`` since you can never run out of sand. If you have -plenty of rock and just want the variety, you can import both ``rockstock`` and -``glassstock`` to get a mixture of rock and glass furnishings in your fort. +Similar to ``library/rockstock`` above, this collection keeps a small stock of +all types of glass furniture. If you have a functioning glass industry, this is +more sustainable than ``library/rockstock`` since you can never run out of sand. +If you have plenty of rock and just want the variety, you can import both +``library/rockstock`` and ``library/glassstock`` to get a mixture of rock and +glass furnishings in your fort. -There are a few items that ``glassstock`` produces that ``rockstock`` does not, -since there are some items that can not be made out of rock, for example: +There are a few items that ``library/glassstock`` produces that +``library/rockstock`` does not, since there are some items that can not be made +out of rock, for example: - tubes and corkscrews for building magma-safe screw pumps - windows diff --git a/plugins/orders.cpp b/plugins/orders.cpp index 9e1bf3e1a..800a0db6e 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -49,21 +49,8 @@ DFhackCExport command_result plugin_init(color_ostream & out, std::vector Date: Thu, 28 Jul 2022 03:49:52 -0700 Subject: [PATCH 368/854] fix formatting in mousequery docs --- docs/plugins/mousequery.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins/mousequery.rst b/docs/plugins/mousequery.rst index eedca7646..39f401bb2 100644 --- a/docs/plugins/mousequery.rst +++ b/docs/plugins/mousequery.rst @@ -31,6 +31,6 @@ Examples ``mousequery rbutton enable`` Enable using the right mouse button to cancel out of menus and scroll the map. -``mousequery edge enable`` ``mousequery delay 300`` - Enable edge scrolling and set the delay to 300ms. + When run after ``mousequery edge enable``, sets the edge scrolling delay to + 300ms. From a6fb509cc7076c1f304571fb2c960b80b2b0be01 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 04:05:41 -0700 Subject: [PATCH 369/854] convert table to list so it's readable in text rendering --- docs/plugins/manipulator.rst | 140 +++++++++++++++++------------------ 1 file changed, 66 insertions(+), 74 deletions(-) diff --git a/docs/plugins/manipulator.rst b/docs/plugins/manipulator.rst index 305499308..c8140a3bf 100644 --- a/docs/plugins/manipulator.rst +++ b/docs/plugins/manipulator.rst @@ -90,82 +90,74 @@ want to import to a dwarf. Once you have assigned a profession to at least one dwarf, you can select "Import Professions from DF" in the DT "File" menu. The professions will then be available for use in DT. -In the charts below, the "At Start" and "Max" columns indicate the approximate -number of dwarves of each profession that you are likely to need at the start of -the game and how many you are likely to need in a mature fort. These are just +In the list below, the "needed" range indicates the approximate number of +dwarves of each profession that you are likely to need at the start of the game +and how many you are likely to need in a mature fort. These are just approximations. Your playstyle may demand more or fewer of each profession. -============= ======== ===== ================================================= -Profession At Start Max Description -============= ======== ===== ================================================= -Chef 0 3 Buchery, Tanning, and Cooking. It is important to - focus just a few dwarves on cooking since - well-crafted meals make dwarves very happy. They - are also an excellent trade good. -Craftsdwarf 0 4-6 All labors used at Craftsdwarf's workshops, - Glassmaker's workshops, and kilns. -Doctor 0 2-4 The full suite of medical labors, plus Animal - Caretaking for those using the `dwarfvet` plugin. -Farmer 1 4 Food- and animal product-related labors. -Fisherdwarf 0 0-1 Fishing and fish cleaning. If you assign this - profession to any dwarf, be prepared to be - inundated with fish. Fisherdwarves *never stop - fishing*. Be sure to also run ``prioritize -a - PrepareRawFish ExtractFromRawFish`` or else - caught fish will just be left to rot. -Hauler 0 >20 All hauling labors plus Siege Operating, Mechanic - (so haulers can assist in reloading traps) and - Architecture (so haulers can help build massive - windmill farms and pump stacks). As you - accumulate enough Haulers, you can turn off - hauling labors for other dwarves so they can - focus on their skilled tasks. You may also want - to restrict your Mechanic's workshops to only - skilled mechanics so your unskilled haulers don't - make low-quality mechanisms. -Laborer 0 10-12 All labors that don't improve quality with skill, - such as Soapmaking and furnace labors. -Marksdwarf 0 10-30 Similar to Hauler. See the description for - Meleedwarf below for more details. -Mason 2 2-4 Masonry and Gem Cutting/Encrusting. -Meleedwarf 0 20-50 Similar to Hauler, but without most civilian - labors. This profession is separate from Hauler - so you can find your military dwarves easily. - Meleedwarves and Marksdwarves have Mechanics and - hauling labors enabled so you can temporarily - deactivate your military after sieges and allow - your military dwarves to help clean up and reset - traps. -Migrant 0 0 You can assign this profession to new migrants - temporarily while you sort them into professions. - Like Marksdwarf and Meleedwarf, the purpose of - this profession is so you can find your new - dwarves more easily. -Miner 2 2-10 Mining and Engraving. This profession also has - the ``Alchemist`` labor enabled, which disables - hauling for those using the `autohauler` plugin. - Once the need for Miners tapers off in the late - game, dwarves with this profession make good - military dwarves, wielding their picks as - weapons. -Outdoorsdwarf 1 2-4 Carpentry, Bowyery, Woodcutting, Animal Training, - Trapping, Plant Gathering, Beekeeping, and Siege - Engineering. -Smith 0 2-4 Smithing labors. You may want to specialize your - Smiths to focus on a single smithing skill to - maximize equipment quality. -StartManager 1 0 All skills not covered by the other starting - professions (Miner, Mason, Outdoorsdwarf, and - Farmer), plus a few overlapping skills to - assist in critical tasks at the beginning of the - game. Individual labors should be turned off as - migrants are assigned more specialized - professions that cover them, and the StartManager - dwarf can eventually convert to some other - profession. -Tailor 0 2 Textile industry labors: Dying, Leatherworking, - Weaving, and Clothesmaking. -============= ======== ===== ================================================= +- ``Chef`` (needed: 0, 3) + Buchery, Tanning, and Cooking. It is important to focus just a few dwarves + on cooking since well-crafted meals make dwarves very happy. They are also + an excellent trade good. +- ``Craftsdwarf`` (needed: 0, 4-6) + All labors used at Craftsdwarf's workshops, Glassmaker's workshops, and + kilns. +``Doctor`` (needed: 0, 2-4) + The full suite of medical labors, plus Animal Caretaking for those using + the `dwarfvet` plugin. +``Farmer`` (needed 1, 4) + Food- and animal product-related labors. +``Fisherdwarf`` (needed 0, 0-1) + Fishing and fish cleaning. If you assign this profession to any dwarf, be + prepared to be inundated with fish. Fisherdwarves *never stop fishing*. Be + sure to also run ``prioritize -a PrepareRawFish ExtractFromRawFish`` or else + caught fish will just be left to rot. +``Hauler`` (needed 0, >20) + All hauling labors plus Siege Operating, Mechanic (so haulers can assist in + reloading traps) and Architecture (so haulers can help build massive + windmill farms and pump stacks). As you accumulate enough Haulers, you can + turn off hauling labors for other dwarves so they can focus on their skilled + tasks. You may also want to restrict your Mechanic's workshops to only + skilled mechanics so your unskilled haulers don't make low-quality + mechanisms. +``Laborer`` (needed 0, 10-12) + All labors that don't improve quality with skill, such as Soapmaking and + furnace labors. +``Marksdwarf`` (needed 0, 10-30) + Similar to ``Hauler``. See the description for ``Meleedwarf`` below for more + details. +``Mason`` (needed 2, 2-4) + Masonry and Gem Cutting/Encrusting. +``Meleedwarf`` (needed 0, 20-50) + Similar to ``Hauler``, but without most civilian labors. This profession is + separate from ``Hauler`` so you can find your military dwarves easily. + ``Meleedwarves`` and ``Marksdwarves`` have Mechanics and hauling labors + enabled so you can temporarily deactivate your military after sieges and + allow your military dwarves to help clean up and reset traps. +``Migrant`` (needed 0, 0) + You can assign this profession to new migrants temporarily while you sort + them into professions. Like ``Marksdwarf`` and ``Meleedwarf``, the purpose + of this profession is so you can find your new dwarves more easily. +``Miner`` (needed 2, 2-10) + Mining and Engraving. This profession also has the ``Alchemist`` labor + enabled, which disables hauling for those using the `autohauler` plugin. + Once the need for Miners tapers off in the late game, dwarves with this + profession make good military dwarves, wielding their picks as weapons. +``Outdoorsdwarf`` (needed 1, 2-4) + Carpentry, Bowyery, Woodcutting, Animal Training, Trapping, Plant Gathering, + Beekeeping, and Siege Engineering. +``Smith`` (needed 0, 2-4) + Smithing labors. You may want to specialize your Smiths to focus on a single + smithing skill to maximize equipment quality. +``StartManager`` (needed 1, 0) + All skills not covered by the other starting professions (``Miner``, + ``Mason``, ``Outdoorsdwarf``, and ``Farmer``), plus a few overlapping skills + to assist in critical tasks at the beginning of the game. Individual labors + should be turned off as migrants are assigned more specialized professions + that cover them, and the StartManager dwarf can eventually convert to some + other profession. +``Tailor`` (needed 0, 2) + Textile industry labors: Dying, Leatherworking, Weaving, and Clothesmaking. A note on autohauler ~~~~~~~~~~~~~~~~~~~~ From 5954d7a19a2875571aec7b742a4ddb3149968f53 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 05:32:30 -0700 Subject: [PATCH 370/854] make docs for plug match the implementation --- docs/builtins/plug.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/builtins/plug.rst b/docs/builtins/plug.rst index 26699d400..963660bbb 100644 --- a/docs/builtins/plug.rst +++ b/docs/builtins/plug.rst @@ -11,5 +11,4 @@ Usage: ``plug`` Lists available plugins and whether they are enabled. ``plug [ ...]`` - Shows the commands implemented by the named plugins and whether the plugins - are enabled. + Lists only the named plugins. From ac2d943fb129037c9bd580eae958bf7d3f4b23ca Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 05:41:29 -0700 Subject: [PATCH 371/854] remove commas from cleaners index entry --- docs/plugins/cleaners.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/plugins/cleaners.rst b/docs/plugins/cleaners.rst index 305bf0db6..4a6aee1e3 100644 --- a/docs/plugins/cleaners.rst +++ b/docs/plugins/cleaners.rst @@ -7,11 +7,11 @@ Tags: :dfhack-keybind:`clean` :dfhack-keybind:`spotclean` -:index:`Removes contaminants from tiles, items, and units. -` More specifically, -it cleans all the splatter that get scattered all over the map and that clings -to your items and units. In an old fortress, this can significantly reduce FPS -lag. It can also spoil your !!FUN!!, so think before you use it. +:index:`Removes contaminants. ` More +specifically, it cleans all the splatter that get scattered all over the map and +that clings to your items and units. In an old fortress, cleaning with this tool +can significantly reduce FPS lag. It can also spoil your !!FUN!!, so think +before you use it. Usage:: From 507b1632a23f58f277a295b0d3d39efeff6e232f Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 26 Jul 2022 11:35:31 -0700 Subject: [PATCH 372/854] support backtick as a keybinding --- docs/Core.rst | 3 ++- docs/changelog.txt | 1 + library/Core.cpp | 3 +++ plugins/hotkeys.cpp | 3 +++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/Core.rst b/docs/Core.rst index fe881a54d..0cc62bef9 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -224,7 +224,8 @@ To set keybindings, use the built-in ``keybinding`` command. Like any other command it can be used at any time from the console, but bindings are not remembered between runs of the game unless re-created in `dfhack.init`. -Currently, any combinations of Ctrl/Alt/Shift with A-Z, 0-9, or F1-F12 are supported. +Currently, any combinations of Ctrl/Alt/Shift with A-Z, 0-9, F1-F12 or \` +are supported. Possible ways to call the command: diff --git a/docs/changelog.txt b/docs/changelog.txt index 447f2f22e..c57bf3435 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - Init scripts: ``dfhack.init`` and other init scripts have moved to ``dfhack-config/init/``. If you have customized your ``dfhack.init`` file and want to keep your changes, please move the part that you have customized to the new location at ``dfhack-config/init/dfhack.init``. If you do not have changes that you want to keep, do not copy anything, and the new defaults will be used automatically. +- `keybinding`: support backquote (``\```) as a hotkey - `manipulator`: add a library of useful default professions - `manipulator`: move professions configuration from ``professions/`` to ``dfhack-config/professions/`` to keep it together with other dfhack configuration. If you have saved professions that you would like to keep, please manually move them to the new folder. - ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. diff --git a/library/Core.cpp b/library/Core.cpp index 6d1f13589..12faa9f7c 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2611,6 +2611,9 @@ static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string if (keyspec.size() == 1 && keyspec[0] >= 'A' && keyspec[0] <= 'Z') { *psym = SDL::K_a + (keyspec[0]-'A'); return true; + } else if (keyspec.size() == 1 && keyspec[0] == '`') { + *psym = SDL::K_BACKQUOTE; + return true; } else if (keyspec.size() == 1 && keyspec[0] >= '0' && keyspec[0] <= '9') { *psym = SDL::K_0 + (keyspec[0]-'0'); return true; diff --git a/plugins/hotkeys.cpp b/plugins/hotkeys.cpp index e4cd13574..ecf3b2969 100644 --- a/plugins/hotkeys.cpp +++ b/plugins/hotkeys.cpp @@ -44,6 +44,7 @@ static void find_active_keybindings(df::viewscreen *screen) sorted_keys.clear(); vector valid_keys; + for (char c = 'A'; c <= 'Z'; c++) { valid_keys.push_back(string(&c, 1)); @@ -54,6 +55,8 @@ static void find_active_keybindings(df::viewscreen *screen) valid_keys.push_back("F" + int_to_string(i)); } + valid_keys.push_back("`"); + auto current_focus = Gui::getFocusString(screen); for (int shifted = 0; shifted < 2; shifted++) { From b6e27b1875754f0d7a77c91290a7730892bf101d Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 05:46:25 -0700 Subject: [PATCH 373/854] fix rendering of quoted backtick --- docs/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index c57bf3435..242ec37da 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,7 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - Init scripts: ``dfhack.init`` and other init scripts have moved to ``dfhack-config/init/``. If you have customized your ``dfhack.init`` file and want to keep your changes, please move the part that you have customized to the new location at ``dfhack-config/init/dfhack.init``. If you do not have changes that you want to keep, do not copy anything, and the new defaults will be used automatically. -- `keybinding`: support backquote (``\```) as a hotkey +- `keybinding`: support backquote (\`) as a hotkey - `manipulator`: add a library of useful default professions - `manipulator`: move professions configuration from ``professions/`` to ``dfhack-config/professions/`` to keep it together with other dfhack configuration. If you have saved professions that you would like to keep, please manually move them to the new folder. - ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. From 380b003b5664211ad9aa0b86a7aa660be26f5f8d Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 06:05:35 -0700 Subject: [PATCH 374/854] fix list formatting for manipulator --- docs/plugins/manipulator.rst | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/plugins/manipulator.rst b/docs/plugins/manipulator.rst index c8140a3bf..87ac4e088 100644 --- a/docs/plugins/manipulator.rst +++ b/docs/plugins/manipulator.rst @@ -102,17 +102,17 @@ approximations. Your playstyle may demand more or fewer of each profession. - ``Craftsdwarf`` (needed: 0, 4-6) All labors used at Craftsdwarf's workshops, Glassmaker's workshops, and kilns. -``Doctor`` (needed: 0, 2-4) +- ``Doctor`` (needed: 0, 2-4) The full suite of medical labors, plus Animal Caretaking for those using the `dwarfvet` plugin. -``Farmer`` (needed 1, 4) +- ``Farmer`` (needed 1, 4) Food- and animal product-related labors. -``Fisherdwarf`` (needed 0, 0-1) +- ``Fisherdwarf`` (needed 0, 0-1) Fishing and fish cleaning. If you assign this profession to any dwarf, be prepared to be inundated with fish. Fisherdwarves *never stop fishing*. Be sure to also run ``prioritize -a PrepareRawFish ExtractFromRawFish`` or else caught fish will just be left to rot. -``Hauler`` (needed 0, >20) +- ``Hauler`` (needed 0, >20) All hauling labors plus Siege Operating, Mechanic (so haulers can assist in reloading traps) and Architecture (so haulers can help build massive windmill farms and pump stacks). As you accumulate enough Haulers, you can @@ -120,43 +120,43 @@ approximations. Your playstyle may demand more or fewer of each profession. tasks. You may also want to restrict your Mechanic's workshops to only skilled mechanics so your unskilled haulers don't make low-quality mechanisms. -``Laborer`` (needed 0, 10-12) +- ``Laborer`` (needed 0, 10-12) All labors that don't improve quality with skill, such as Soapmaking and furnace labors. -``Marksdwarf`` (needed 0, 10-30) +- ``Marksdwarf`` (needed 0, 10-30) Similar to ``Hauler``. See the description for ``Meleedwarf`` below for more details. -``Mason`` (needed 2, 2-4) +- ``Mason`` (needed 2, 2-4) Masonry and Gem Cutting/Encrusting. -``Meleedwarf`` (needed 0, 20-50) +- ``Meleedwarf`` (needed 0, 20-50) Similar to ``Hauler``, but without most civilian labors. This profession is separate from ``Hauler`` so you can find your military dwarves easily. ``Meleedwarves`` and ``Marksdwarves`` have Mechanics and hauling labors enabled so you can temporarily deactivate your military after sieges and allow your military dwarves to help clean up and reset traps. -``Migrant`` (needed 0, 0) +- ``Migrant`` (needed 0, 0) You can assign this profession to new migrants temporarily while you sort them into professions. Like ``Marksdwarf`` and ``Meleedwarf``, the purpose of this profession is so you can find your new dwarves more easily. -``Miner`` (needed 2, 2-10) +- ``Miner`` (needed 2, 2-10) Mining and Engraving. This profession also has the ``Alchemist`` labor enabled, which disables hauling for those using the `autohauler` plugin. Once the need for Miners tapers off in the late game, dwarves with this profession make good military dwarves, wielding their picks as weapons. -``Outdoorsdwarf`` (needed 1, 2-4) +- ``Outdoorsdwarf`` (needed 1, 2-4) Carpentry, Bowyery, Woodcutting, Animal Training, Trapping, Plant Gathering, Beekeeping, and Siege Engineering. -``Smith`` (needed 0, 2-4) +- ``Smith`` (needed 0, 2-4) Smithing labors. You may want to specialize your Smiths to focus on a single smithing skill to maximize equipment quality. -``StartManager`` (needed 1, 0) +- ``StartManager`` (needed 1, 0) All skills not covered by the other starting professions (``Miner``, ``Mason``, ``Outdoorsdwarf``, and ``Farmer``), plus a few overlapping skills to assist in critical tasks at the beginning of the game. Individual labors should be turned off as migrants are assigned more specialized professions that cover them, and the StartManager dwarf can eventually convert to some other profession. -``Tailor`` (needed 0, 2) +- ``Tailor`` (needed 0, 2) Textile industry labors: Dying, Leatherworking, Weaving, and Clothesmaking. A note on autohauler From a09b35c2bd5e273a4fe7666ee7791e3f3a8d4272 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 15:32:26 -0700 Subject: [PATCH 375/854] ignore generated directories --- .gitignore | 2 ++ conf.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 5155f2bb9..0711d9b17 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,10 @@ build/VC2010 docs/changelogs/ docs/html/ docs/pdf/ +docs/pseudoxml/ docs/text/ docs/tools/ +docs/xml/ # in-place build build/Makefile diff --git a/conf.py b/conf.py index fab4b286a..b01027a6b 100644 --- a/conf.py +++ b/conf.py @@ -310,7 +310,10 @@ exclude_patterns = [ 'docs/tags/*', 'docs/text/*', 'docs/builtins/*', + 'docs/pdf/*', 'docs/plugins/*', + 'docs/pseudoxml/*', + 'docs/xml/*', 'scripts/docs/*', ] From 2e2abbe87ad4610999fea2a03e064881bc74951f Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 16:46:10 -0700 Subject: [PATCH 376/854] update docs for petcapRemover and make it actually start running when it is enabled --- docs/plugins/petcapRemover.rst | 41 ++++++++++++++++++++++------------ plugins/petcapRemover.cpp | 18 +++++---------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/docs/plugins/petcapRemover.rst b/docs/plugins/petcapRemover.rst index 65ebaeeab..e3ce47916 100644 --- a/docs/plugins/petcapRemover.rst +++ b/docs/plugins/petcapRemover.rst @@ -1,19 +1,32 @@ petcapRemover ============= -Allows you to remove or raise the pet population cap. In vanilla -DF, pets will not reproduce unless the population is below 50 and the number of -children of that species is below a certain percentage. This plugin allows -removing the second restriction and removing or raising the first. Pets still -require PET or PET_EXOTIC tags in order to reproduce. Type ``help petcapRemover`` -for exact usage. In order to make population more stable and avoid sudden -population booms as you go below the raised population cap, this plugin counts -pregnancies toward the new population cap. It can still go over, but only in the -case of multiple births. +Tags: +:dfhack-keybind:`petcapRemover` + +Modify the pet population cap. In vanilla DF, pets will not reproduce unless the +population is below 50 and the number of children of that species is below a +certain percentage. This plugin allows removing these restrictions and setting +your own thresholds. Pets still require PET or PET_EXOTIC tags in order to +reproduce. In order to make population more stable and avoid sudden population +booms as you go below the raised population cap, this plugin counts pregnancies +toward the new population cap. It can still go over, but only in the case of +multiple births. Usage: -:petcapRemover: cause pregnancies now and schedule the next check -:petcapRemover every n: set how often in ticks the plugin checks for possible pregnancies -:petcapRemover cap n: set the new cap to n. if n = 0, no cap -:petcapRemover pregtime n: sets the pregnancy duration to n ticks. natural pregnancies are - 300000 ticks for the current race and 200000 for everyone else +``enable petcapRemover`` + Enables the plugin and starts running with default settings. +``petcapRemover cap `` + Set the new population cap per species to the specified value. If set to 0, + then there is no cap (good luck with all those animals!). The default cap + is 100. +``petcapRemover`` + Impregnate female pets that have access to a compatible male, up to the + population cap. +``petcapRemover every `` + Set how often the plugin will cause pregnancies. The default frequency is + every 10,000 ticks (a little over 8 game days). +``petcapRemover pregtime `` + Sets the pregnancy duration to the specified number of ticks. The default + value is 200,000 ticks, which is the natural pet pregnancy duration. + diff --git a/plugins/petcapRemover.cpp b/plugins/petcapRemover.cpp index 7d6588cbc..2c1de318d 100644 --- a/plugins/petcapRemover.cpp +++ b/plugins/petcapRemover.cpp @@ -36,18 +36,8 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector params; + return petcapRemover(out, params); } } From 2d400fb571c37c2681ff0b8f112cde9d8d106077 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 22:51:17 -0700 Subject: [PATCH 377/854] update docs for plants n.b. original docs were a lie. --- docs/plugins/plants.rst | 34 ++++++++++++++++++++++------------ plugins/plants.cpp | 19 ++++++------------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/docs/plugins/plants.rst b/docs/plugins/plants.rst index 0111b0770..4fc87f353 100644 --- a/docs/plugins/plants.rst +++ b/docs/plugins/plants.rst @@ -1,18 +1,28 @@ .. _plant: -plant -===== -A tool for creating shrubs, growing, or getting rid of them. +plants +====== +Tags: +:dfhack-keybind:`plant` -Subcommands: +Grow shrubs or trees. -:create: Creates a new sapling under the cursor. Takes a raw ID as argument - (e.g. TOWER_CAP). The cursor must be located on a dirt or grass floor tile. -:grow: Turns saplings into trees; under the cursor if a sapling is selected, - or every sapling on the map if the cursor is hidden. +Usage: -For mass effects, use one of the additional options: +``plant create `` + Creates a new plant of the specified type at the active cursor position. + The cursor must be on a dirt or grass floor tile. +``plant grow`` + Grows saplings into trees. If the cursor is active, it only affects the + sapling under the cursor. If no cursor is active, it affect all saplings + on the map. -:shrubs: affect all shrubs on the map -:trees: affect all trees on the map -:all: affect every plant! +To see the full list of plant ids, run the following command:: + + devel/query --table df.global.world.raws.plants.all --search ^id --maxdepth 1 + +Example +------- + +``plant create TOWER_CAP`` + Create a Tower Cap sapling at the cursor position. diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 69e712ef8..2325c01e3 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -32,10 +32,7 @@ command_result df_grow (color_ostream &out, vector & parameters) { if(parameters[i] == "help" || parameters[i] == "?") { - out.print("Usage:\n" - "This command turns all living saplings on the map into full-grown trees.\n" - "With active cursor, work on the targetted one only.\n"); - return CR_OK; + return CR_WRONG_USAGE; } } @@ -91,11 +88,7 @@ command_result df_createplant (color_ostream &out, vector & parameters) { if ((parameters.size() != 1) || (parameters[0] == "help" || parameters[0] == "?")) { - out.print("Usage:\n" - "Create a new plant at the cursor.\n" - "Specify the type of plant to create by its raw ID (e.g. TOWER_CAP or MUSHROOM_HELMET_PLUMP).\n" - "Only shrubs and saplings can be placed, and they must be located on a dirt or grass floor.\n"); - return CR_OK; + return CR_WRONG_USAGE; } CoreSuspender suspend; @@ -201,10 +194,10 @@ command_result df_plant (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("plant", "Plant creation and removal.", df_plant, false, - "Command to create, grow or remove plants on the map. For more details, check the subcommand help :\n" - "plant grow help - Grows saplings into trees.\n" - "plant create help - Create a new plant.\n")); + commands.push_back(PluginCommand( + "plant", + "Grow shrubs or trees.", + df_plant)); return CR_OK; } From 1bbe996d581a617afeb1b34019665cdcb437e763 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 22:55:20 -0700 Subject: [PATCH 378/854] update docs for power-meter --- docs/plugins/power-meter.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/plugins/power-meter.rst b/docs/plugins/power-meter.rst index 163ef91be..9443f2a93 100644 --- a/docs/plugins/power-meter.rst +++ b/docs/plugins/power-meter.rst @@ -1,5 +1,13 @@ power-meter =========== +Tags: + +Provides the backend for `gui/power-meter`. + +Usage:: + + enable power-meter + The power-meter plugin implements a modified pressure plate that detects power being supplied to gear boxes built in the four adjacent N/S/W/E tiles. From cf69a1a2cf6ca82f5bff87247902c0e13f39b215 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 23:04:07 -0700 Subject: [PATCH 379/854] update docs for probe --- docs/plugins/probe.rst | 26 +++++++++++++++++--------- plugins/probe.cpp | 18 ++++++------------ 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/docs/plugins/probe.rst b/docs/plugins/probe.rst index 1dcc28336..614d0bc81 100644 --- a/docs/plugins/probe.rst +++ b/docs/plugins/probe.rst @@ -1,13 +1,21 @@ probe ===== +Tags: +:dfhack-keybind:`probe` +:dfhack-keybind:`bprobe` +:dfhack-keybind:`cprobe` -This plugin provides multiple commands that print low-level properties of the -selected objects. +Display low-level properties of selected objects. -* ``probe``: prints some properties of the tile selected with :kbd:`k`. Some of - these properties can be passed into `tiletypes`. -* ``cprobe``: prints some properties of the unit selected with :kbd:`v`, as well - as the IDs of any worn items. `gui/gm-unit` and `gui/gm-editor` are more - complete in-game alternatives. -* ``bprobe``: prints some properties of the building selected with :kbd:`q` or - :kbd:`t`. `gui/gm-editor` is a more complete in-game alternative. +Usage: + +``probe`` + Displays properties of the tile selected with :kbd:`k`. Some of these + properties can be passed into `tiletypes`. +``bprobe`` + Displays properties of the building selected with :kbd:`q` or :kbd:`t`. + For deeper inspection of the building, see `gui/gm-editor`. +``cprobe`` + Displays properties of the unit selected with :kbd:`v`. It also displays the + IDs of any worn items. For deeper inspection of the unit and inventory items, + see `gui/gm-unit` and `gui/gm-editor`. diff --git a/plugins/probe.cpp b/plugins/probe.cpp index ba7c42ede..b9b98ee75 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -49,20 +49,14 @@ command_result df_bprobe (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("probe", - "A tile probe", - df_probe, - false, - "Hover the cursor over a tile to view its properties.\n")); + "Display information about the selected tile.", + df_probe)); commands.push_back(PluginCommand("cprobe", - "A creature probe", - df_cprobe, - false, - "Select a creature to view its properties.\n")); + "Display information about the selected creature.", + df_cprobe)); commands.push_back(PluginCommand("bprobe", - "A simple building probe", - df_bprobe, - false, - "Select a building to view its properties.\n")); + "Display information about the selected building.", + df_bprobe)); return CR_OK; } From 1270cf3f0a6f67685b8d007995edaa1e9ef7bb74 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 23:12:11 -0700 Subject: [PATCH 380/854] update docs for prospector --- docs/plugins/prospector.rst | 59 +++++++++++++++++++++---------------- plugins/prospector.cpp | 43 ++------------------------- 2 files changed, 36 insertions(+), 66 deletions(-) diff --git a/docs/plugins/prospector.rst b/docs/plugins/prospector.rst index 03dd193f8..95436abec 100644 --- a/docs/plugins/prospector.rst +++ b/docs/plugins/prospector.rst @@ -1,43 +1,50 @@ .. _prospect: -prospect -======== +prospector +========== +Tags: +:dfhack-keybind:`prospect` -**Usage:** +Shows a summary of resources that exist on the map or an estimate of resources +available in the selected embark area. - ``prospect [all|hell] []`` +Usage:: -Shows a summary of resources that exist on the map. By default, only the visible -part of the map is scanned. Include the ``all`` keyword if you want ``prospect`` -to scan the whole map as if it were revealed. Use ``hell`` instead of ``all`` if -you also want to see the Z range of HFS tubes in the 'features' report section. + prospect [all|hell] [] -**Options:** +By default, only the visible part of the map is scanned. Include the ``all`` +keyword if you want ``prospect`` to scan the whole map as if it were revealed. +Use ``hell`` instead of ``all`` if you also want to see the Z range of HFS +tubes in the 'features' report section. -:``-h``, ``--help``: - Shows this help text. -:``-s``, ``--show ``: - Shows only the named comma-separated list of report sections. Report section - names are: summary, liquids, layers, features, ores, gems, veins, shrubs, - and trees. If run during pre-embark, only the layers, ores, gems, and veins - report sections are available. -:``-v``, ``--values``: - Includes material value in the output. Most useful for the 'gems' report - section. - -**Examples:** +Examples +-------- ``prospect all`` Shows the entire report for the entire map. ``prospect hell --show layers,ores,veins`` Shows only the layers, ores, and other vein stone report sections, and - includes information on HFS tubes when a fort is loaded. + includes information on HFS tubes (if run on a fortress map and not the + pre-embark screen). ``prospect all -sores`` Show only information about ores for the pre-embark or fortress map report. -**Pre-embark estimate:** +Options +------- + +``-s``, ``--show `` + Shows only the named comma-separated list of report sections. Report section + names are: summary, liquids, layers, features, ores, gems, veins, shrubs, + and trees. If run during pre-embark, only the layers, ores, gems, and veins + report sections are available. +``-v``, ``--values`` + Includes material value in the output. Most useful for the 'gems' report + section. + +Pre-embark estimate +------------------- If prospect is called during the embark selection screen, it displays an estimate of layer stone availability. If the ``all`` keyword is specified, it @@ -48,6 +55,6 @@ the embark rectangle. The results of pre-embark prospect are an *estimate*, and can at best be expected to be somewhere within +/- 30% of the true amount; sometimes it - does a lot worse. Especially, it is not clear how to precisely compute how - many soil layers there will be in a given embark tile, so it can report a - whole extra layer, or omit one that is actually present. + does a lot worse. In particular, it is not clear how to precisely compute + how many soil layers there will be in a given embark tile, so it can report + a whole extra layer, or omit one that is actually present. diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 0516dd552..e75c967dc 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -260,46 +260,9 @@ command_result prospector (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "prospect", "Show stats of available raw resources.", - prospector, false, - " prospect [all|hell] []\n" - "\n" - " Shows a summary of resources that exist on the map. By default,\n" - " only the visible part of the map is scanned. Include the 'all' keyword\n" - " if you want prospect to scan the whole map as if it were revealed.\n" - " Use 'hell' instead of 'all' if you also want to see the Z range of HFS\n" - " tubes in the 'features' report section.\n" - "\n" - "Options:\n" - " -h,--help\n" - " Shows this help text.\n" - " -s,--show \n" - " Shows only the named comma-separated list of report sections.\n" - " Report section names are: summary, liquids, layers, features, ores,\n" - " gems, veins, shrubs, and trees. If run during pre-embark, only the\n" - " layers, ores, gems, and veins report sections are available.\n" - " -v,--values\n" - " Includes material value in the output. Most useful for the 'gems'\n" - " report section.\n" - "\n" - "Examples:\n" - " prospect all\n" - " Shows the entire report for the entire map.\n" - "\n" - " prospect hell --show layers,ores,veins\n" - " Shows only the layers, ores, and other vein stone report sections,\n" - " and includes information on HFS tubes when a fort is loaded.\n" - "\n" - " prospect all -sores\n" - " Show only information about ores for the pre-embark or fortress map\n" - " report.\n" - "\n" - "Pre-embark estimate:\n" - " If called during the embark selection screen, displays a rough\n" - " estimate of layer stone availability. If the 'all' keyword is\n" - " specified, also estimates ores, gems, and other vein material. The\n" - " estimate covers all tiles of the embark rectangle.\n" - )); + "prospect", + "Show raw resources available on the map.", + prospector)); return CR_OK; } From e513253d8d9b7b4b9c78a2dd18c945ce19575191 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 23:20:24 -0700 Subject: [PATCH 381/854] update docs for regrass --- docs/plugins/regrass.rst | 13 ++++++++++++- plugins/regrass.cpp | 6 ++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst index f3d00d8e1..d47e98d19 100644 --- a/docs/plugins/regrass.rst +++ b/docs/plugins/regrass.rst @@ -1,3 +1,14 @@ regrass ======= -Regrows all the grass. Not much to it ;) +Tags: +:dfhack-keybind:`regrass` + +Regrows all the grass. Use this command if your grazers have eaten everything +down to the dirt. + +Usage:: + + regrass [max] + +Specify the 'max' option to pack more grass onto a tile than what the game +normally allows to give your grazers extra chewing time. diff --git a/plugins/regrass.cpp b/plugins/regrass.cpp index 0cac5bd77..5d2a6450c 100644 --- a/plugins/regrass.cpp +++ b/plugins/regrass.cpp @@ -29,8 +29,10 @@ command_result df_regrass (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("regrass", "Regrows surface grass.", df_regrass, false, - "Specify parameter 'max' to set all grass types to full density, otherwise only one type of grass will be restored per tile.\n")); + commands.push_back(PluginCommand( + "regrass", + "Regrows surface grass.", + df_regrass)); return CR_OK; } From 7a4e8ea18ef0f4b2fde767c36add8f2fade839a0 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 29 Jul 2022 16:05:49 -0700 Subject: [PATCH 382/854] some plugins can't be directly enabled by the user --- docs/plugins/RemoteFortressReader.rst | 2 -- docs/plugins/autoclothing.rst | 9 +++++---- docs/plugins/autogems.rst | 3 ++- docs/plugins/burrows.rst | 6 +----- docs/plugins/power-meter.rst | 13 +++---------- 5 files changed, 11 insertions(+), 22 deletions(-) diff --git a/docs/plugins/RemoteFortressReader.rst b/docs/plugins/RemoteFortressReader.rst index 103c904db..f3bf505ac 100644 --- a/docs/plugins/RemoteFortressReader.rst +++ b/docs/plugins/RemoteFortressReader.rst @@ -10,8 +10,6 @@ remote fortress visualization. See :forums:`Armok Vision <146473>`. Usage: -``enable RemoteFortressReader`` - Enable the plugin. ``RemoteFortressReader_version`` Print the loaded RemoteFortressReader version. ``load-art-image-chunk `` diff --git a/docs/plugins/autoclothing.rst b/docs/plugins/autoclothing.rst index bbfe14cc9..2e0d03ec1 100644 --- a/docs/plugins/autoclothing.rst +++ b/docs/plugins/autoclothing.rst @@ -9,14 +9,15 @@ set how many of each clothing type every citizen should have. Usage:: - enable autoclothing - autoclothing [number] + autoclothing + autoclothing [quantity] ``material`` can be "cloth", "silk", "yarn", or "leather". The ``item`` can be anything your civilization can produce, such as "dress" or "mitten". -When invoked without a number, it shows the current configuration for that -material and item. +When invoked without parameters, it shows a summary of all managed clothing +orders. When invoked with a material and item, but without a quantity, it shows +the current configuration for that material and item. Examples -------- diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index 72b850754..ee1bc8be6 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -10,7 +10,8 @@ orders for cutting them at a Jeweler's Workshop. Usage: ``enable autogems`` - Enables the plugin + Enables the plugin and starts autocutting gems according to its + configuration. ``autogems-reload`` :index:`Reloads the autogems configuration file. ` You might need diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst index 733f784d0..29b81330d 100644 --- a/docs/plugins/burrows.rst +++ b/docs/plugins/burrows.rst @@ -15,15 +15,11 @@ You can also use the ``burrow`` command to Usage: -``enable burrows`` - Enable the plugin for the auto-grow feature (see - ``burrow enable auto-grow`` below) ``burrow enable auto-grow`` When a wall inside a burrow with a name ending in '+' is dug out, the burrow will be extended to newly-revealed adjacent walls. This final '+' may be omitted in burrow name args of other ``burrows`` commands. Note that digging - 1-wide corridors with the miner inside the burrow is SLOW. Be sure to also - run ``enable burrows`` for this feature to work. + 1-wide corridors with the miner inside the burrow is SLOW. ``burrow disable auto-grow`` Disables auto-grow processing. ``burrow clear-unit [ ...]`` diff --git a/docs/plugins/power-meter.rst b/docs/plugins/power-meter.rst index 9443f2a93..e099a7903 100644 --- a/docs/plugins/power-meter.rst +++ b/docs/plugins/power-meter.rst @@ -2,13 +2,6 @@ power-meter =========== Tags: -Provides the backend for `gui/power-meter`. - -Usage:: - - enable power-meter - -The power-meter plugin implements a modified pressure plate that detects power being -supplied to gear boxes built in the four adjacent N/S/W/E tiles. - -The configuration front-end is implemented by `gui/power-meter`. +Allows presure plates to measure power. If you run `gui/power-meter` while +building a pressure plate, the pressure plate can be modified to detects power +being supplied to gear boxes built in the four adjacent N/S/W/E tiles. From eef7812bf67fa828cb595827b8be629e4d20f5ee Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 29 Jul 2022 16:08:00 -0700 Subject: [PATCH 383/854] update docs for rename --- docs/plugins/rename.rst | 41 ++++++++++++++++++++++++++--------------- plugins/rename.cpp | 13 +++---------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/docs/plugins/rename.rst b/docs/plugins/rename.rst index 55534e636..0c4bc9ddb 100644 --- a/docs/plugins/rename.rst +++ b/docs/plugins/rename.rst @@ -1,19 +1,30 @@ rename ====== -Allows renaming various things. Use `gui/rename` for an in-game interface. +Tags: +:dfhack-keybind:`rename` -Options: +Easily rename things. Use `gui/rename` for an in-game interface. -``rename squad "name"`` - Rename squad by index to 'name'. -``rename hotkey \"name\"`` - Rename hotkey by index. This allows assigning - longer commands to the DF hotkeys. -``rename unit "nickname"`` - Rename a unit/creature highlighted in the DF user interface. -``rename unit-profession "custom profession"`` - Change proffession name of the highlighted unit/creature. -``rename building "name"`` - Set a custom name for the selected building. - The building must be one of stockpile, workshop, furnace, trap, - siege engine or an activity zone. +Usage: + +``rename squad ""`` + Rename the indicated squad. The ordinal is the number that corresponds to + the list of squads in the squads menu (:kbd:`s`). The first squad is ordinal + ``1``. +``rename hotkey ""`` + Rename the indicated hotkey. The ordinal the the number that corresponds to + the list of hotkeys in the hotkeys menu (:kbd:`H`). The first hotkey is + ordinal ``1``. +``rename unit ""`` + Give the selected unit the given nickname. +``rename unit-profession ""`` + Give the selected unit the given profession name. +``rename building ""`` + Set a custom name to the selected building. The building must be a + stockpile, workshop, furnace, trap, siege engine, or activity zone. + +Example +------- + +``rename squad 1 "The Whiz Bonkers"`` + Rename the first squad to The Whiz Bonkers. diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 0ef3e02d5..7c46fc908 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -61,16 +61,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector \"name\"\n" - " rename hotkey \"name\"\n" - " (identified by ordinal index)\n" - " rename unit \"nickname\"\n" - " rename unit-profession \"custom profession\"\n" - " (a unit must be highlighted in the ui)\n" - " rename building \"nickname\"\n" - " (a building must be highlighted via 'q')\n" - )); + "rename", + "Easily rename things.", + rename)); if (Core::getInstance().isWorldLoaded()) plugin_onstatechange(out, SC_WORLD_LOADED); From a7011421b4a59f2cb472397744d2a88d084ac741 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 29 Jul 2022 16:31:37 -0700 Subject: [PATCH 384/854] update docs for rendermax --- docs/plugins/rendermax.rst | 36 +++++++++++++++++++++++---------- plugins/rendermax/rendermax.cpp | 15 ++++---------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/docs/plugins/rendermax.rst b/docs/plugins/rendermax.rst index a173a75db..641a7a248 100644 --- a/docs/plugins/rendermax.rst +++ b/docs/plugins/rendermax.rst @@ -1,18 +1,32 @@ rendermax ========= -A collection of renderer replacing/enhancing filters. For better effect try changing the -black color in palette to non totally black. See :forums:`128487` for more info. +Tags: +:dfhack-keybind:`rendermax` -Options: +Modify the map lighting. This plugin provides a collection of OpenGL lighting +filters that affect how the map is drawn to the screen. -:trippy: Randomizes the color of each tiles. Used for fun, or testing. -:light: Enable lighting engine. -:light reload: Reload the settings file. -:light sun |cycle: Set time to (in hours) or set it to df time cycle. -:occlusionON, occlusionOFF: Show debug occlusion info. -:disable: Disable any filter that is enabled. +Usage: -An image showing lava and dragon breath. Not pictured here: sunlight, shining items/plants, -materials that color the light etc... +``rendermax light`` + Light the map tiles realisitically. Outside tiles are light during the day + and dark at night. Inside tiles are always dark unless a nearby unit is + lighting it up, as if they were carrying torches. +``rendermax light sun |cycle`` + Set the outside lighting to correspond with the specified day hour (1-24), + or specify ``cycle`` to have the lighting follow the sun (which is the + default). +``rendermax light reload`` + Reload the lighting settings file. +``rendermax trippy`` + Randomize the color of each tile. Used for fun, or testing. +``rendermax disable`` + Disable any ``rendermax`` lighting filters that are currently active. + +An image showing lava and dragon breath. Not pictured here: sunlight, shining +items/plants, materials that color the light etc. .. image:: ../images/rendermax.png + +For better visibility, try changing the black color in palette to non totally +black. See :forums:`128487` for more info. diff --git a/plugins/rendermax/rendermax.cpp b/plugins/rendermax/rendermax.cpp index d391bdb36..b7c703e09 100644 --- a/plugins/rendermax/rendermax.cpp +++ b/plugins/rendermax/rendermax.cpp @@ -49,16 +49,9 @@ static command_result rendermax(color_ostream &out, vector & parameters DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "rendermax", "switch rendering engine.", rendermax, false, - " rendermax trippy\n" - " rendermax truecolor red|green|blue|white\n" - " rendermax lua\n" - " rendermax light - lighting engine\n" - " rendermax light reload - reload the settings file\n" - " rendermax light sun |cycle - set time to x (in hours) or cycle (same effect if x<0)\n" - " rendermax light occlusionON|occlusionOFF - debug the occlusion map\n" - " rendermax disable\n" - )); + "rendermax", + "Modify the map lighting.", + rendermax)); return CR_OK; } @@ -331,7 +324,7 @@ static command_result rendermax(color_ostream &out, vector & parameters return CR_WRONG_USAGE; if(!enabler->renderer->uses_opengl()) { - out.printerr("Sorry, this plugin needs open gl enabled printmode. Try STANDARD or other non-2D\n"); + out.printerr("Sorry, this plugin needs open GL-enabled printmode. Try STANDARD or other non-2D.\n"); return CR_FAILURE; } string cmd=parameters[0]; From c6d5fcb37873438c4851d0ff823192b50f744466 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 29 Jul 2022 17:12:01 -0700 Subject: [PATCH 385/854] update docs for resume --- docs/plugins/resume.rst | 15 +++++++++++++-- plugins/resume.cpp | 15 +++------------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/plugins/resume.rst b/docs/plugins/resume.rst index ccca39e28..d8feb0896 100644 --- a/docs/plugins/resume.rst +++ b/docs/plugins/resume.rst @@ -1,4 +1,15 @@ resume ====== -Allows automatic resumption of suspended constructions, along with colored -UI hints for construction status. +Tags: +:dfhack-keybind:`` + +Color planned buildings based on their suspend status. When enabled, this plugin +will display a colored 'X' over suspended buildings. When run as a command, it +can resume all suspended building jobs, allowing you to quickly recover if a +bunch of jobs were suspended due to the workers getting scared off by wildlife +or items temporarily blocking buildling sites. + +Usage:: + + enable resume + resume all diff --git a/plugins/resume.cpp b/plugins/resume.cpp index 6b8ad5e7d..21a4b521e 100644 --- a/plugins/resume.cpp +++ b/plugins/resume.cpp @@ -302,18 +302,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Fri, 29 Jul 2022 17:12:15 -0700 Subject: [PATCH 386/854] update docs for reveal --- docs/plugins/reveal.rst | 58 +++++++++++++++++++++++++---------------- plugins/reveal.cpp | 42 ++++++++++++++++------------- 2 files changed, 59 insertions(+), 41 deletions(-) diff --git a/docs/plugins/reveal.rst b/docs/plugins/reveal.rst index 42f490aa4..f8611d033 100644 --- a/docs/plugins/reveal.rst +++ b/docs/plugins/reveal.rst @@ -2,29 +2,41 @@ reveal ====== -This reveals the map. By default, HFS will remain hidden so that the demons -don't spawn. You can use ``reveal hell`` to reveal everything. With hell revealed, -you won't be able to unpause until you hide the map again. If you really want -to unpause with hell revealed, use ``reveal demons``. +Tags: +:dfhack-keybind:`reveal` +:dfhack-keybind:`unreveal` +:dfhack-keybind:`revforget` +:dfhack-keybind:`revtoggle` +:dfhack-keybind:`revflood` +:dfhack-keybind:`nopause` -Reveal also works in adventure mode, but any of its effects are negated once -you move. When you use it this way, you don't need to run ``unreveal``. +Reveals the map. This reveals all z-layers in fort mode. It also works in +adventure mode, but any of its effects are negated once you move. When you use +it this way, you don't need to run ``unreveal`` to hide the map again. -Usage and related commands: +Usage: -:reveal: Reveal the whole map, except for HFS to avoid demons spawning -:reveal hell: Also show hell, but requires ``unreveal`` before unpausing -:reveal demon: Reveals everything and allows unpausing - good luck! -:unreveal: Reverts the effects of ``reveal`` -:revtoggle: Switches between ``reveal`` and ``unreveal`` -:revflood: Hide everything, then reveal tiles with a path to the cursor. - Note that tiles behind constructed walls are also revealed as a - workaround for :bug:`1871`. -:revforget: Discard info about what was visible before revealing the map. - Only useful where (e.g.) you abandoned with the fort revealed - and no longer want the data. - -nopause -======= -Disables pausing (both manual and automatic) with the exception of pause forced -by `reveal` ``hell``. This is nice for digging under rivers. +``reveal [hell|demon]`` + Reveal the whole map. If ``hell`` is specified, also reveal HFS areas, but + you are required to run ``unreveal`` before unpausing is allowed in order + to prevent the demons from spawning. If you really want to unpause with hell + revealed, specify ``demon`` instead of ``hell``. +``unreveal`` + Reverts the effects of ``reveal``. +``revtoggle`` + Switches between ``reveal`` and ``unreveal``. Convenient to bind to a + hotkey. +``revforget`` + Discard info about what was visible before revealing the map. Only useful + where (for example) you abandoned with the fort revealed and no longer need + the saved map data when you load a new fort. +``revflood`` + Hide everything, then reveal tiles with a path to the cursor. This allows + reparing maps that you accidentally saved while they were revealed. Note + that tiles behind constructed walls are also revealed as a workaround for + :bug:`1871`. +``nopause 1|0`` + Disables pausing (both manual and automatic) with the exception of the pause + forced by `reveal` ``hell``. This is nice for digging under rivers. Use + ``nopause 1`` to prevent pausing and ``nopause 0`` to allow pausing like + normal. diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index 39720926c..8d76bd54b 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -82,24 +82,30 @@ command_result nopause(color_ostream &out, vector & params); DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - commands.push_back(PluginCommand("reveal","Reveal the map.",reveal,false, - "Reveals the map, by default ignoring hell.\n" - "Options:\n" - "hell - also reveal hell, while forcing the game to pause.\n" - "demon - reveal hell, do not pause.\n")); - commands.push_back(PluginCommand("unreveal","Revert the map to its previous state.",unreveal,false, - "Reverts the previous reveal operation, hiding the map again.\n")); - commands.push_back(PluginCommand("revtoggle","Reveal/unreveal depending on state.",revtoggle,false, - "Toggles between reveal and unreveal.\n")); - commands.push_back(PluginCommand("revflood","Hide all, and reveal tiles reachable from the cursor.",revflood,false, - "This command hides the whole map. Then, starting from the cursor,\n" - "reveals all accessible tiles. Allows repairing perma-revealed maps.\n" - "Note that constructed walls are considered passable to work around DF bug 1871.\n")); - commands.push_back(PluginCommand("revforget", "Forget the current reveal data.",revforget,false, - "Forget the current reveal data, allowing to use reveal again.\n")); - commands.push_back(PluginCommand("nopause","Disable manual and automatic pausing.",nopause,false, - "Disable pausing (doesn't affect pause forced by reveal).\n" - "Activate with 'nopause 1', deactivate with 'nopause 0'.\n")); + commands.push_back(PluginCommand( + "reveal", + "Reveal the map.", + reveal)); + commands.push_back(PluginCommand( + "unreveal", + "Revert a revealed map to its unrevealed state.", + unreveal)); + commands.push_back(PluginCommand( + "revtoggle", + "Switch betwen reveal and unreveal.", + revtoggle)); + commands.push_back(PluginCommand( + "revflood", + "Hide all, then reveal tiles reachable from the cursor.", + revflood)); + commands.push_back(PluginCommand( + "revforget", + "Forget the current reveal data.", + revforget)); + commands.push_back(PluginCommand( + "nopause", + "Disable manual and automatic pausing.", + nopause)); return CR_OK; } From aa3a389b6fa06b08e12f365ac8789cb9b84a32b2 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 29 Jul 2022 17:37:50 -0700 Subject: [PATCH 387/854] fix parsing bold text and indenting of ls output --- library/lua/helpdb.lua | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 2aee005ee..cf8e86af5 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -179,14 +179,15 @@ local function update_entry(entry, iterator, opts) end if not header_found and line:find('%w') then header_found = true - elseif not tags_found and line:find('^Tags: [%w, ]+$') then - local _,_,tags = line:trim():find('Tags: (.*)') + elseif not tags_found and line:find('^[*]*Tags:[*]* [%w, ]+$') then + local _,_,tags = line:trim():find('[*]*Tags:[*]* *(.*)') entry.tags = {} for _,tag in ipairs(tags:split('[ ,]+')) do entry.tags[tag] = true end tags_found = true - elseif not short_help_found and not line:find('^Keybinding:') and + elseif not short_help_found and + not line:find('^[*]*Keybinding:') and line:find('%w') then if in_short_help then entry.short_help = entry.short_help .. ' ' .. line @@ -646,9 +647,11 @@ function help(entry) print(get_entry_long_help(entry)) end --- prints col1 (width 20), a 2 space gap, and col2 (width 58) --- if col1text is longer than 20 characters, col2text is printed on the next --- line. if col2text is longer than 58 characters, it is wrapped. +-- prints col1text (width 21), a one space gap, and col2 (width 58) +-- if col1text is longer than 21 characters, col2text is printed starting on the +-- next line. if col2text is longer than 58 characters, it is wrapped. col2text +-- lines on lines below the col1text output are indented by one space further +-- than the col2text on the first line. local COL1WIDTH, COL2WIDTH = 20, 58 local function print_columns(col1text, col2text) col2text = col2text:wrap(COL2WIDTH) @@ -656,12 +659,14 @@ local function print_columns(col1text, col2text) for line in col2text:gmatch('[^'..NEWLINE..']*') do table.insert(wrapped_col2, line) end + local col2_start_line = 1 if #col1text > COL1WIDTH then print(col1text) else - print(('%-'..COL1WIDTH..'s %s'):format(col1text, wrapped_col2[1])) + print(('%-'..COL1WIDTH..'s %s'):format(col1text, wrapped_col2[1])) + col2_start_line = 2 end - for i=2,#wrapped_col2 do + for i=col2_start_line,#wrapped_col2 do print(('%'..COL1WIDTH..'s %s'):format(' ', wrapped_col2[i])) end end From f785a910ffd492f198fe5ad09d185a3cc7d94a98 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 29 Jul 2022 17:38:23 -0700 Subject: [PATCH 388/854] update docs for search --- docs/plugins/search.rst | 48 ++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/docs/plugins/search.rst b/docs/plugins/search.rst index 57b9e5df4..c8f5d68b3 100644 --- a/docs/plugins/search.rst +++ b/docs/plugins/search.rst @@ -1,40 +1,44 @@ - .. _search-plugin: search ====== -The search plugin adds search to the Stocks, Animals, Trading, Stockpile, -Noble (assignment candidates), Military (position candidates), Burrows -(unit list), Rooms, Announcements, Job List and Unit List screens. +Tags: + +The search plugin adds search capabilities to the Stocks, Animals, Trading, +Stockpile, Noble (assignment candidates), Military (position candidates), +Burrows (unit list), Rooms, Announcements, Job List, and Unit List screens. + +Usage:: + + enable search .. image:: ../images/search.png Searching works the same way as the search option in :guilabel:`Move to Depot`. -You will see the Search option displayed on screen with a hotkey (usually :kbd:`s`). -Pressing it lets you start typing a query and the relevant list will start -filtering automatically. +You will see the Search option displayed on screen with a hotkey +(usually :kbd:`s`). Pressing it lets you start typing a query and the relevant +list will start filtering automatically. -Pressing :kbd:`Enter`, :kbd:`Esc` or the arrow keys will return you to browsing the now -filtered list, which still functions as normal. You can clear the filter -by either going back into search mode and backspacing to delete it, or -pressing the "shifted" version of the search hotkey while browsing the -list (e.g. if the hotkey is :kbd:`s`, then hitting :kbd:`Shift`:kbd:`s` will clear any -filter). +Pressing :kbd:`Enter`, :kbd:`Esc` or the arrow keys will return you to browsing +the now filtered list, which still functions as normal. You can clear the filter +by either going back into search mode and backspacing to delete it, or pressing +the "shifted" version of the search hotkey while browsing the list (e.g. if the +hotkey is :kbd:`s`, then hitting :kbd:`Shift`:kbd:`s` will clear any filter). Leaving any screen automatically clears the filter. -In the Trade screen, the actual trade will always only act on items that -are actually visible in the list; the same effect applies to the Trade -Value numbers displayed by the screen. Because of this, the :kbd:`t` key is -blocked while search is active, so you have to reset the filters first. -Pressing :kbd:`Alt`:kbd:`C` will clear both search strings. +In the Trade screen, the actual trade will always only act on items that are +actually visible in the list; the same effect applies to the Trade Value numbers +displayed by the screen. Because of this, the :kbd:`t` key is blocked while +search is active, so you have to reset the filters first. Pressing +:kbd:`Alt`:kbd:`C` will clear both search strings. In the stockpile screen the option only appears if the cursor is in the rightmost list: .. image:: ../images/search-stockpile.png -Note that the 'Permit XXX'/'Forbid XXX' keys conveniently operate only -on items actually shown in the rightmost list, so it is possible to select -only fat or tallow by forbidding fats, then searching for fat/tallow, and -using Permit Fats again while the list is filtered. +Note that the 'Permit XXX'/'Forbid XXX' keys conveniently operate only on items +actually shown in the rightmost list, so it is possible to select only fat or +tallow by forbidding fats, then searching for fat/tallow, and using Permit Fats +again while the list is filtered. From 8cc0cee9a85664129aad3a46b1212168bf9b4c1a Mon Sep 17 00:00:00 2001 From: Myk Date: Sat, 30 Jul 2022 07:37:46 -0700 Subject: [PATCH 389/854] Fix grammar in power meter docs --- docs/plugins/power-meter.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins/power-meter.rst b/docs/plugins/power-meter.rst index e099a7903..7186a413d 100644 --- a/docs/plugins/power-meter.rst +++ b/docs/plugins/power-meter.rst @@ -2,6 +2,6 @@ power-meter =========== Tags: -Allows presure plates to measure power. If you run `gui/power-meter` while -building a pressure plate, the pressure plate can be modified to detects power +Allow presure plates to measure power. If you run `gui/power-meter` while +building a pressure plate, the pressure plate can be modified to detect power being supplied to gear boxes built in the four adjacent N/S/W/E tiles. From feda5851e995ebce3bf983d844b324aa469869b5 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:28:01 -0700 Subject: [PATCH 390/854] add example for ruby plugin --- docs/plugins/ruby.rst | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/plugins/ruby.rst b/docs/plugins/ruby.rst index bdd5521a4..beb9be4a1 100644 --- a/docs/plugins/ruby.rst +++ b/docs/plugins/ruby.rst @@ -2,5 +2,22 @@ ruby ==== -Ruby language plugin, which evaluates the following arguments as a ruby string. -Best used as ``:rb [string]``, for the special parsing mode. Alias ``rb_eval``. +Tags: +:dfhack-keybind:`rb` +:dfhack-keybind:`rb_eval` + +Allow Ruby scripts to be executed. When invoked as a command, you can Eval() a +ruby string. + +Usage:: + + enable ruby + rb "ruby expression" + rb_eval "ruby expression" + :rb ruby expression + +Example +------- + +``:rb puts df.unit_find(:selected).name`` + Print the name of the selected unit. From edb7bd3168ba633681a3d16890891d1b90c698e5 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:28:29 -0700 Subject: [PATCH 391/854] update docs for seedwatch --- docs/plugins/seedwatch.rst | 54 ++++++++++++++++++++++++------------ plugins/seedwatch.cpp | 56 +++++--------------------------------- 2 files changed, 43 insertions(+), 67 deletions(-) diff --git a/docs/plugins/seedwatch.rst b/docs/plugins/seedwatch.rst index 1d5ab9e62..2b04b3fa6 100644 --- a/docs/plugins/seedwatch.rst +++ b/docs/plugins/seedwatch.rst @@ -1,27 +1,45 @@ seedwatch ========= -Watches the numbers of seeds available and enables/disables seed and plant cooking. +Tags: +:dfhack-keybind:`seedwatch` -Each plant type can be assigned a limit. If their number falls below that limit, -the plants and seeds of that type will be excluded from cookery. -If the number rises above the limit + 20, then cooking will be allowed. +Manages seed and plant cooking based on seed stock levels. -The plugin needs a fortress to be loaded and will deactivate automatically otherwise. -You have to reactivate with 'seedwatch start' after you load the game. +Each seed type can be assigned a target. If the number of seeds of that type +falls below that target, then the plants and seeds of that type will be excluded +from cookery. If the number rises above the target + 20, then cooking will be +allowed. -Options: +The plugin needs a fortress to be loaded and will deactivate automatically +otherwise. You have to reactivate with ``enable seedwatch`` after you load a +fort. -:all: Adds all plants from the abbreviation list to the watch list. -:start: Start watching. -:stop: Stop watching. -:info: Display whether seedwatch is watching, and the watch list. -:clear: Clears the watch list. +Usage: -Examples: +``enable seedwatch`` + Start managing seed and plant cooking. By default, no types are watched. + You have to add them with further ``seedwatch`` commands. +``seedwatch `` + Adds the specifiied type to the watchlist (if it's not already there) and + sets the target number of seeds to the specified number. You can pass the + keyword ``all`` instead of a specific type to set the target for all types. +``seedwatch `` + Removes the specified type from the watch list. +``seedwatch clear`` + Clears all types from the watch list. +``seedwatch info`` + Display whether seedwatch is enabled and prints out the watch list. + +To print out a list of all plant types, you can run this command:: + + devel/query --table df.global.world.raws.plants.all --search ^id --maxdepth 1 + +Examples +-------- -``seedwatch MUSHROOM_HELMET_PLUMP 30`` - add ``MUSHROOM_HELMET_PLUMP`` to the watch list, limit = 30 -``seedwatch MUSHROOM_HELMET_PLUMP`` - removes ``MUSHROOM_HELMET_PLUMP`` from the watch list. ``seedwatch all 30`` - adds all plants from the abbreviation list to the watch list, the limit being 30. + Adds all seeds to the watch list and sets the targets to 30. +``seedwatch MUSHROOM_HELMET_PLUMP 50`` + Add Plump Helmets to the watch list and sets the target to 50. +``seedwatch MUSHROOM_HELMET_PLUMP`` + removes Plump Helmets from the watch list. diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index 478a0899a..17a765bcf 100644 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -49,47 +49,6 @@ bool ignoreSeeds(df::item_flags& f) // seeds with the following flags should not f.bits.in_job; }; -void printHelp(color_ostream &out) // prints help -{ - out.print( - "Watches the numbers of seeds available and enables/disables seed and plant cooking.\n" - "Each plant type can be assigned a limit. If their number falls below,\n" - "the plants and seeds of that type will be excluded from cookery.\n" - "If the number rises above the limit + %i, then cooking will be allowed.\n", buffer - ); - out.printerr( - "The plugin needs a fortress to be loaded and will deactivate automatically otherwise.\n" - "You have to reactivate with 'seedwatch start' after you load the game.\n" - ); - out.print( - "Options:\n" - "seedwatch all - Adds all plants from the abbreviation list to the watch list.\n" - "seedwatch start - Start watching.\n" - "seedwatch stop - Stop watching.\n" - "seedwatch info - Display whether seedwatch is watching, and the watch list.\n" - "seedwatch clear - Clears the watch list.\n\n" - ); - if(!abbreviations.empty()) - { - out.print("You can use these abbreviations for the plant tokens:\n"); - for(map::const_iterator i = abbreviations.begin(); i != abbreviations.end(); ++i) - { - out.print("%s -> %s\n", i->first.c_str(), i->second.c_str()); - } - } - out.print( - "Examples:\n" - "seedwatch MUSHROOM_HELMET_PLUMP 30\n" - " add MUSHROOM_HELMET_PLUMP to the watch list, limit = 30\n" - "seedwatch MUSHROOM_HELMET_PLUMP\n" - " removes MUSHROOM_HELMET_PLUMP from the watch list.\n" - "seedwatch ph 30\n" - " is the same as 'seedwatch MUSHROOM_HELMET_PLUMP 30'\n" - "seedwatch all 30\n" - " adds all plants from the abbreviation list to the watch list, the limit being 30.\n" - ); -}; - // searches abbreviations, returns expansion if so, returns original if not string searchAbbreviations(string in) { @@ -142,8 +101,7 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) if(gm.g_mode != game_mode::DWARF || !World::isFortressMode(gm.g_type)) { // just print the help - printHelp(out); - return CR_OK; + return CR_WRONG_USAGE; } string par; @@ -151,14 +109,12 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) switch(parameters.size()) { case 0: - printHelp(out); return CR_WRONG_USAGE; case 1: par = parameters[0]; if ((par == "help") || (par == "?")) { - printHelp(out); return CR_WRONG_USAGE; } else if(par == "start") @@ -180,11 +136,11 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) out.print("seedwatch Info:\n"); if(running) { - out.print("seedwatch is supervising. Use 'seedwatch stop' to stop supervision.\n"); + out.print("seedwatch is supervising. Use 'disable seedwatch' to stop supervision.\n"); } else { - out.print("seedwatch is not supervising. Use 'seedwatch start' to start supervision.\n"); + out.print("seedwatch is not supervising. Use 'enable seedwatch' to start supervision.\n"); } map watchMap; Kitchen::fillWatchMap(watchMap); @@ -246,7 +202,6 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) } break; default: - printHelp(out); return CR_WRONG_USAGE; break; } @@ -256,7 +211,10 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) DFhackCExport command_result plugin_init(color_ostream &out, vector& commands) { - commands.push_back(PluginCommand("seedwatch", "Toggles seed cooking based on quantity available", df_seedwatch)); + commands.push_back(PluginCommand( + "seedwatch", + "Toggles seed cooking based on quantity available.", + df_seedwatch)); // fill in the abbreviations map, with abbreviations for the standard plants abbreviations["bs"] = "SLIVER_BARB"; abbreviations["bt"] = "TUBER_BLOATED"; From 8f7a23ee4d1061b20e6eeaf2b133e9cf3b30c2d6 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:28:41 -0700 Subject: [PATCH 392/854] update docs for showmood --- docs/plugins/showmood.rst | 9 ++++++++- plugins/showmood.cpp | 6 ++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/plugins/showmood.rst b/docs/plugins/showmood.rst index 9276766c4..8afe4c629 100644 --- a/docs/plugins/showmood.rst +++ b/docs/plugins/showmood.rst @@ -1,3 +1,10 @@ showmood ======== -Shows all items needed for the currently active strange mood. +Tags: +:dfhack-keybind:`showmood` + +Shows all items needed for the active strange mood. + +Usage:: + + showmood diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index c7d620676..abeb2e98e 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -297,8 +297,10 @@ command_result df_showmood (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("showmood", "Shows items needed for current strange mood.", df_showmood, false, - "Run this command without any parameters to display information on the currently active Strange Mood.")); + commands.push_back(PluginCommand( + "showmood", + "Shows all items needed for active strange mood.", + df_showmood)); return CR_OK; } From a1f937e354678661bc4ca622e45ccfb51ebdbe82 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:28:55 -0700 Subject: [PATCH 393/854] update docs for siege-engine --- docs/plugins/siege-engine.rst | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/plugins/siege-engine.rst b/docs/plugins/siege-engine.rst index 0e9511d3d..a4cfed2ba 100644 --- a/docs/plugins/siege-engine.rst +++ b/docs/plugins/siege-engine.rst @@ -1,12 +1,19 @@ siege-engine ============ -Siege engines in DF haven't been updated since the game was 2D, and can -only aim in four directions. To make them useful above-ground, -this plugin allows you to: +Tags: + +Extend the functionality and usability of siege engines. Siege engines in DF +haven't been updated since the game was 2D, and can only aim in four +directions. To make them useful above-ground, this plugin allows you to: * link siege engines to stockpiles * restrict operator skill levels (like workshops) * load any object into a catapult, not just stones * aim at a rectangular area in any direction, and across Z-levels -The front-end is implemented by `gui/siege-engine`. +Usage:: + + enable siege-engine + +You can use the new features by selecting a built siege engine and running +`gui/siege-engine`. From 72619148265d8836ecb45f6f8252fd0db077e5ab Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:29:08 -0700 Subject: [PATCH 394/854] update docs for sort --- docs/plugins/sort.rst | 62 ++++++++++++++++++++++++++++--------------- plugins/sort.cpp | 20 ++------------ 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/docs/plugins/sort.rst b/docs/plugins/sort.rst index cc86b3ddd..12684175f 100644 --- a/docs/plugins/sort.rst +++ b/docs/plugins/sort.rst @@ -1,33 +1,51 @@ -sort-items -========== -Sort the visible item list:: - - sort-items order [order...] +sort +==== +Tags: +:dfhack-keybind:`sort-items` +:dfhack-keybind:`sort-units` -Sort the item list using the given sequence of comparisons. -The ``<`` prefix for an order makes undefined values sort first. -The ``>`` prefix reverses the sort order for defined values. +Sort the visible item or unit list. -Item order examples:: +Usage:: - description material wear type quality + sort-items [ ...] + sort-units [ ...] -The orderings are defined in ``hack/lua/plugins/sort/*.lua`` +Both commands sort the visible list using the given sequence of comparisons. +Each property can be prefixed with a ``<`` or ``>`` character to indicate +whether elements that don't have the given property defined go first or last +(respectively) in the sorted list. -sort-units -========== -Sort the visible unit list:: +Examples +-------- - sort-units order [order...] +``sort-items material type quality`` + Sort a list of items by material, then by type, then by quality +``sort-units profession name`` + Sort a list of units by profession, then by name -Sort the unit list using the given sequence of comparisons. -The ``<`` prefix for an order makes undefined values sort first. -The ``>`` prefix reverses the sort order for defined values. +Properties +---------- -Unit order examples:: +Items can be sorted by the following properties: - name age arrival squad squad_position profession +- ``type`` +- ``description`` +- ``base_quality`` +- ``quality`` +- ``improvement`` +- ``wear`` +- ``material`` -The orderings are defined in ``hack/lua/plugins/sort/*.lua`` +Units can be sorted by the following properties: -:dfhack-keybind:`sort-units` +- ``name`` +- ``age`` +- ``arrival`` +- ``noble`` +- ``profession`` +- ``profession_class`` +- ``race`` +- ``squad`` +- ``squad_position`` +- ``happiness`` diff --git a/plugins/sort.cpp b/plugins/sort.cpp index ab2829655..630ac9e53 100644 --- a/plugins/sort.cpp +++ b/plugins/sort.cpp @@ -56,25 +56,9 @@ static command_result sort_items(color_ostream &out, vector & parameter DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "sort-units", "Sort the visible unit list.", sort_units, unit_list_hotkey, - " sort-units order [order...]\n" - " Sort the unit list using the given sequence of comparisons.\n" - " The '<' prefix for an order makes undefined values sort first.\n" - " The '>' prefix reverses the sort order for defined values.\n" - " Unit order examples:\n" - " name, age, arrival, squad, squad_position, profession\n" - "The orderings are defined in hack/lua/plugins/sort/*.lua\n" - )); + "sort-units", "Sort the visible unit list.", sort_units, unit_list_hotkey)); commands.push_back(PluginCommand( - "sort-items", "Sort the visible item list.", sort_items, item_list_hotkey, - " sort-items order [order...]\n" - " Sort the item list using the given sequence of comparisons.\n" - " The '<' prefix for an order makes undefined values sort first.\n" - " The '>' prefix reverses the sort order for defined values.\n" - " Item order examples:\n" - " description, material, wear, type, quality\n" - "The orderings are defined in hack/lua/plugins/sort/*.lua\n" - )); + "sort-items", "Sort the visible item list.", sort_items, item_list_hotkey)); return CR_OK; } From af0631cbd9d38263dcfe4dcd940d42f114ec8a6b Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:29:18 -0700 Subject: [PATCH 395/854] update docs for spectate --- docs/plugins/spectate.rst | 13 ++++++++++--- plugins/spectate.cpp | 7 +------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/plugins/spectate.rst b/docs/plugins/spectate.rst index 6c2ca9e24..983086c94 100644 --- a/docs/plugins/spectate.rst +++ b/docs/plugins/spectate.rst @@ -1,5 +1,12 @@ spectate ======== -Simple plugin to automate following random dwarves. Most of the time things will -be weighted towards z-levels with the highest job activity. Simply enter the -``spectate`` command to toggle the plugin's state. +Tags: + +Automatically follow exciting dwarves. + +Usage:: + + enable spectate + +The plugin will automatically switch which dwarf is being followed periodically, +preferring dwarves on z-levels with the highest job activity. diff --git a/plugins/spectate.cpp b/plugins/spectate.cpp index aae48cf5a..1161c3f32 100644 --- a/plugins/spectate.cpp +++ b/plugins/spectate.cpp @@ -42,12 +42,7 @@ command_result spectate (color_ostream &out, std::vector & paramet DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("spectate", "Automated spectator mode.", - spectate, - false, - "" - " spectate\n" - " toggles spectator mode\n" - "\n")); + spectate)); return CR_OK; } From 479494e5a6a135605e0cc6722c163226aea11cb9 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:29:28 -0700 Subject: [PATCH 396/854] update docs for steam-engine --- docs/plugins/steam-engine.rst | 106 +++++++++++++++++----------------- plugins/steam-engine.cpp | 76 ------------------------ 2 files changed, 54 insertions(+), 128 deletions(-) diff --git a/docs/plugins/steam-engine.rst b/docs/plugins/steam-engine.rst index 416ac9ad1..35cd490f5 100644 --- a/docs/plugins/steam-engine.rst +++ b/docs/plugins/steam-engine.rst @@ -1,40 +1,47 @@ steam-engine ============ -The steam-engine plugin detects custom workshops with STEAM_ENGINE in -their token, and turns them into real steam engines. +Tags: -The vanilla game contains only water wheels and windmills as sources of -power, but windmills give relatively little power, and water wheels require -flowing water, which must either be a real river and thus immovable and +Allow modded steam engine buildings to function. The steam-engine plugin detects +custom workshops with STEAM_ENGINE in their token, and turns them into real +steam engines! + +The plugin auto-enables itself when it detects the relevant tags in the world +raws. It does not need to be enabled with the `enable` command. + +Rationale +--------- +The vanilla game contains only water wheels and windmills as sources of power, +but windmills give relatively little power, and water wheels require flowing +water, which must either be a real river and thus immovable and limited in supply, or actually flowing and thus laggy. -Compared to the :wiki:`water reactor ` -exploit, steam engines make a lot of sense! +Compared to the +:wiki:`dwarven water reactor ` exploit, +steam engines make a lot of sense! Construction ------------ -The workshop needs water as its input, which it takes via a -passable floor tile below it, like usual magma workshops do. -The magma version also needs magma. +The workshop needs water as its input, which it takes via a passable floor tile +below it, like usual magma workshops do. The magma version also needs magma. -Due to DFHack limits, the workshop will collapse over true open space. -However down stairs are passable but support machines, so you can use them. +Due to DF game limits, the workshop will collapse over true open space. However, +down stairs are passable but support machines, so you can use them. -After constructing the building itself, machines can be connected -to the edge tiles that look like gear boxes. Their exact position -is extracted from the workshop raws. +After constructing the building itself, machines can be connected to the edge +tiles that look like gear boxes. Their exact position is extracted from the +workshop raws. -Like with collapse above, due to DFHack limits the workshop -can only immediately connect to machine components built AFTER it. -This also means that engines cannot be chained without intermediate -axles built after both engines. +Like with collapse above, due to DF game limits the workshop can only +immediately connect to machine components built AFTER it. This also means that +engines cannot be chained without intermediate axles built after both engines. Operation --------- -In order to operate the engine, queue the Stoke Boiler job (optionally -on repeat). A furnace operator will come, possibly bringing a bar of fuel, -and perform it. As a result, a "boiling water" item will appear -in the :kbd:`t` view of the workshop. +In order to operate the engine, queue the Stoke Boiler job (optionally on +repeat). A furnace operator will come, possibly bringing a bar of fuel, and +perform it. As a result, a "boiling water" item will appear in the :kbd:`t` +view of the workshop. .. note:: @@ -44,41 +51,36 @@ in the :kbd:`t` view of the workshop. have infinite power. However, liquid consumption should be slow enough that water can be supplied by a pond zone bucket chain. -Every such item gives 100 power, up to a limit of 300 for coal, -and 500 for a magma engine. The building can host twice that -amount of items to provide longer autonomous running. When the -boiler gets filled to capacity, all queued jobs are suspended; -once it drops back to 3+1 or 5+1 items, they are re-enabled. - -While the engine is providing power, steam is being consumed. -The consumption speed includes a fixed 10% waste rate, and -the remaining 90% are applied proportionally to the actual -load in the machine. With the engine at nominal 300 power with -150 load in the system, it will consume steam for actual +Every such item gives 100 power, up to a limit of 300 for coal, or 500 for a +magma engine. The building can host twice that amount of items to provide longer +autonomous running. When the boiler gets filled to capacity, all queued jobs are +suspended. Once it drops back to 3+1 or 5+1 items, they are re-enabled. + +While the engine is providing power, steam is being consumed. The consumption +speed includes a fixed 10% waste rate, and the remaining 90% is applied +proportionally to the actual load in the machine. With the engine at nominal 300 +power with 150 load in the system, it will consume steam for actual 300*(10% + 90%*150/300) = 165 power. -Masterpiece mechanism and chain will decrease the mechanical -power drawn by the engine itself from 10 to 5. Masterpiece -barrel decreases waste rate by 4%. Masterpiece piston and pipe -decrease it by further 4%, and also decrease the whole steam -use rate by 10%. +A masterpiece mechanism and chain will decrease the mechanical power drawn by +the engine itself from 10 to 5. A masterpiece barrel decreases waste rate by 4%. +A masterpiece piston and pipe decrease it by further 4%, and also decrease the +whole steam use rate by 10%. Explosions ---------- -The engine must be constructed using barrel, pipe and piston -from fire-safe, or in the magma version magma-safe metals. +The engine must be constructed using barrel, pipe, and piston from fire-safe, +or, in the magma version, magma-safe metals. -During operation weak parts get gradually worn out, and -eventually the engine explodes. It should also explode if -toppled during operation by a building destroyer, or a -tantruming dwarf. +During operation, weak parts gradually wear out, and eventually the engine +explodes. It should also explode if toppled during operation by a building +destroyer or a tantruming dwarf. Save files ---------- -It should be safe to load and view engine-using fortresses -from a DF version without DFHack installed, except that in such -case the engines won't work. However actually making modifications -to them, or machines they connect to (including by pulling levers), -can easily result in inconsistent state once this plugin is -available again. The effects may be as weird as negative power -being generated. +It should be safe to load and view engine-using fortresses from a DF version +without DFHack installed, except that in such case the engines, of course, won't +work. However actually making modifications to them or machines they connect to +(including by pulling levers) can easily result in inconsistent state once this +plugin is available again. The effects may be as weird as negative power being +generated. diff --git a/plugins/steam-engine.cpp b/plugins/steam-engine.cpp index fbc575e36..6e3c317d3 100644 --- a/plugins/steam-engine.cpp +++ b/plugins/steam-engine.cpp @@ -37,82 +37,6 @@ #include "df/workshop_type.h" #include "df/world.h" -/* - * This plugin implements a steam engine workshop. It activates - * if there are any workshops in the raws with STEAM_ENGINE in - * their token, and provides the necessary behavior. - * - * Construction: - * - * The workshop needs water as its input, which it takes via a - * passable floor tile below it, like usual magma workshops do. - * The magma version also needs magma. - * - * ISSUE: Since this building is a machine, and machine collapse - * code cannot be modified, it would collapse over true open space. - * As a loophole, down stair provides support to machines, while - * being passable, so use them. - * - * After constructing the building itself, machines can be connected - * to the edge tiles that look like gear boxes. Their exact position - * is extracted from the workshop raws. - * - * ISSUE: Like with collapse above, part of the code involved in - * machine connection cannot be modified. As a result, the workshop - * can only immediately connect to machine components built AFTER it. - * This also means that engines cannot be chained without intermediate - * short axles that can be built later. - * - * Operation: - * - * In order to operate the engine, queue the Stoke Boiler job. - * A furnace operator will come, possibly bringing a bar of fuel, - * and perform it. As a result, a "boiling water" item will appear - * in the 't' view of the workshop. - * - * Note: The completion of the job will actually consume one unit - * of appropriate liquids from below the workshop. - * - * Every such item gives 100 power, up to a limit of 300 for coal, - * and 500 for a magma engine. The building can host twice that - * amount of items to provide longer autonomous running. When the - * boiler gets filled to capacity, all queued jobs are suspended; - * once it drops back to 3+1 or 5+1 items, they are re-enabled. - * - * While the engine is providing power, steam is being consumed. - * The consumption speed includes a fixed 10% waste rate, and - * the remaining 90% are applied proportionally to the actual - * load in the machine. With the engine at nominal 300 power with - * 150 load in the system, it will consume steam for actual - * 300*(10% + 90%*150/300) = 165 power. - * - * Masterpiece mechanism and chain will decrease the mechanical - * power drawn by the engine itself from 10 to 5. Masterpiece - * barrel decreases waste rate by 4%. Masterpiece piston and pipe - * decrease it by further 4%, and also decrease the whole steam - * use rate by 10%. - * - * Explosions: - * - * The engine must be constructed using barrel, pipe and piston - * from fire-safe, or in the magma version magma-safe metals. - * - * During operation weak parts get gradually worn out, and - * eventually the engine explodes. It should also explode if - * toppled during operation by a building destroyer, or a - * tantruming dwarf. - * - * Save files: - * - * It should be safe to load and view fortresses using engines - * from a DF version without DFHack installed, except that in such - * case the engines won't work. However actually making modifications - * to them, or machines they connect to (including by pulling levers), - * can easily result in inconsistent state once this plugin is - * available again. The effects may be as weird as negative power - * being generated. - */ - using std::vector; using std::string; using std::stack; From 08d434d16ea24d902c0f0b9fe8da5ac9e09d5972 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:29:40 -0700 Subject: [PATCH 397/854] update docs for stockflow --- docs/plugins/stockflow.rst | 39 +++++++++++++++++++------------------- plugins/stockflow.cpp | 32 +++++-------------------------- 2 files changed, 25 insertions(+), 46 deletions(-) diff --git a/docs/plugins/stockflow.rst b/docs/plugins/stockflow.rst index c3db7a673..0e2f5a1ad 100644 --- a/docs/plugins/stockflow.rst +++ b/docs/plugins/stockflow.rst @@ -1,24 +1,14 @@ stockflow ========= -Allows the fortress bookkeeper to queue jobs through the manager, -based on space or items available in stockpiles. +Tags: +:dfhack-keybind:`stockflow` -Inspired by `workflow`. +Queue manager jobs based on free space in stockpiles. With this plugin, the +fortress bookkeeper can tally up free space in specific stockpiles and queue +jobs through the manager to produce items to fill the free space. -Usage: - -``stockflow enable`` - Enable the plugin. -``stockflow disable`` - Disable the plugin. -``stockflow fast`` - Enable the plugin in fast mode. -``stockflow list`` - List any work order settings for your stockpiles. -``stockflow status`` - Display whether the plugin is enabled. - -While enabled, the :kbd:`q` menu of each stockpile will have two new options: +When the plugin is enabled, the :kbd:`q` menu of each stockpile will have two +new options: * :kbd:`j`: Select a job to order, from an interface like the manager's screen. * :kbd:`J`: Cycle between several options for how many such jobs to order. @@ -27,5 +17,16 @@ Whenever the bookkeeper updates stockpile records, new work orders will be placed on the manager's queue for each such selection, reduced by the number of identical orders already in the queue. -In fast mode, new work orders will be enqueued once per day, instead of -waiting for the bookkeeper. +This plugin is similar to `workflow`, but uses stockpiles to manage job triggers +instead of abstract stock quantities. + +Usage: + +``enable stockflow`` + Enable the plugin. +``stockflow status`` + Display whether the plugin is enabled. +``stockflow list`` + List any work order settings for your stockpiles. +``stockflow fast`` + Enqueue orders once per day instead of waiting for the bookkeeper. diff --git a/plugins/stockflow.cpp b/plugins/stockflow.cpp index aede089ad..65223f603 100644 --- a/plugins/stockflow.cpp +++ b/plugins/stockflow.cpp @@ -29,30 +29,6 @@ REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(ui); bool fast = false; -const char *tagline = "Allow the bookkeeper to queue manager jobs."; -const char *usage = ( - " stockflow enable\n" - " Enable the plugin.\n" - " stockflow disable\n" - " Disable the plugin.\n" - " stockflow fast\n" - " Enable the plugin in fast mode.\n" - " stockflow list\n" - " List any work order settings for your stockpiles.\n" - " stockflow status\n" - " Display whether the plugin is enabled.\n" - "\n" - "While enabled, the 'q' menu of each stockpile will have two new options:\n" - " j: Select a job to order, from an interface like the manager's screen.\n" - " J: Cycle between several options for how many such jobs to order.\n" - "\n" - "Whenever the bookkeeper updates stockpile records, new work orders will\n" - "be placed on the manager's queue for each such selection, reduced by the\n" - "number of identical orders already in the queue.\n" - "\n" - "In fast mode, new work orders will be enqueued once per day, instead of\n" - "waiting for the bookkeeper.\n" -); /* * Lua interface. @@ -342,8 +318,7 @@ static command_result stockflow_cmd(color_ostream &out, vector & parame desired = true; fast = true; } else if (parameters[0] == "usage" || parameters[0] == "help" || parameters[0] == "?") { - out.print("%s: %s\nUsage:\n%s", plugin_name, tagline, usage); - return CR_OK; + return CR_WRONG_USAGE; } else if (parameters[0] == "list") { if (!enabled) { out.printerr("Stockflow is not currently enabled.\n"); @@ -416,7 +391,10 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Sun, 31 Jul 2022 13:29:53 -0700 Subject: [PATCH 398/854] update docs for stockpiles --- docs/plugins/stockpiles.rst | 52 ++++++++++++++++++++----------- plugins/stockpiles/stockpiles.cpp | 48 +++++++++------------------- 2 files changed, 48 insertions(+), 52 deletions(-) diff --git a/docs/plugins/stockpiles.rst b/docs/plugins/stockpiles.rst index 000527ce5..76d52117d 100644 --- a/docs/plugins/stockpiles.rst +++ b/docs/plugins/stockpiles.rst @@ -2,27 +2,41 @@ stockpiles ========== -Offers the following commands to save and load stockpile settings. -See `gui/stockpiles` for an in-game interface. +Tags: +:dfhack-keybind:`copystock` +:dfhack-keybind:`savestock` +:dfhack-keybind:`loadstock` -:copystock: Copies the parameters of the currently highlighted stockpile to the custom - stockpile settings and switches to custom stockpile placement mode, effectively - allowing you to copy/paste stockpiles easily. - :dfhack-keybind:`copystock` +Import and export stockpile settings. When the plugin is enabled, the :kbd:`q` +menu of each stockpile will have an option for saving or loading the stockpile +settings. See `gui/stockpiles` for an in-game interface. -:savestock: Saves the currently highlighted stockpile's settings to a file in your Dwarf - Fortress folder. This file can be used to copy settings between game saves or - players. e.g.: ``savestock food_settings.dfstock`` +Usage: -:loadstock: Loads a saved stockpile settings file and applies it to the currently selected - stockpile. e.g.: ``loadstock food_settings.dfstock`` +``enable stockpiles`` + Add a hotkey that you can hit to easily save and load settings from + stockpiles selected in :kbd:`q` mode. +``copystock`` + Copies the parameters of the currently highlighted stockpile to the custom + stockpile settings and switches to custom stockpile placement mode, + effectively allowing you to copy/paste stockpiles easily. +``savestock `` + Saves the currently highlighted stockpile's settings to a file in your + Dwarf Fortress folder. This file can be used to copy settings between game + saves or players. +``loadstock `` + Loads a saved stockpile settings file and applies it to the currently + selected stockpile. -To use savestock and loadstock, use the :kbd:`q` command to highlight a stockpile. -Then run savestock giving it a descriptive filename. Then, in a different (or -the same!) gameworld, you can highlight any stockpile with :kbd:`q` then execute the -``loadstock`` command passing it the name of that file. The settings will be -applied to that stockpile. +Filenames with spaces are not supported. Generated materials, divine metals, +etc. are not saved as they are different in every world. -Note that files are relative to the DF folder, so put your files there or in a -subfolder for easy access. Filenames should not have spaces. Generated materials, -divine metals, etc are not saved as they are different in every world. +Examples +-------- + +``savestock food_settings.dfstock`` + Export the stockpile settings for the stockpile currently selected in + :kbd:`q` mode to a file named ``food_settings.dfstock``. +``loadstock food_settings.dfstock`` + Set the selected stockpile settings to those saved in the + ``food_settings.dfstock`` file. diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index 05e5c9f96..aa36e20bd 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -61,39 +61,21 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector : filename to save stockpile settings to (will be overwritten!)\n" - ) - ); - commands.push_back ( - PluginCommand ( - "loadstock", "Load and apply stockpile settings from a file.", - loadstock, loadstock_guard, - "Must be in 'q' mode and have a stockpile selected.\n" - "example: 'loadstock food.dfstock' will load the settings from 'food.dfstock'\n" - "in your stockpile folder and apply them to the selected stockpile.\n" - " -d, --debug: enable debug output\n" - " : filename to load stockpile settings from\n" - ) - ); + commands.push_back(PluginCommand( + "copystock", + "Copy stockpile under cursor.", + copystock, + copystock_guard)); + commands.push_back(PluginCommand( + "savestock", + "Save the active stockpile's settings to a file.", + savestock, + savestock_guard)); + commands.push_back(PluginCommand( + "loadstock", + "Load and apply stockpile settings from a file.", + loadstock, + loadstock_guard)); } return CR_OK; From e1e245b4b41784f0c1f5733938e65a708ac3c84d Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:30:04 -0700 Subject: [PATCH 399/854] update docs for stocks --- docs/plugins/stocks.rst | 19 +++++++++++++++++-- plugins/stocks.cpp | 8 ++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/plugins/stocks.rst b/docs/plugins/stocks.rst index 404c96a6d..076f2ab6f 100644 --- a/docs/plugins/stocks.rst +++ b/docs/plugins/stocks.rst @@ -1,5 +1,20 @@ stocks ====== -Replaces the DF stocks screen with an improved version. - +Tags: :dfhack-keybind:`stocks` + +Enhanced fortress stock management interface. When the plugin is enabled, two +new hotkeys become available: + +* :kbd:`e` on the vanilla DF stocks screen (:kbd:`z` and then select Stocks) + will launch the fortress-wide stock management screen. +* :kbd:`i` when a stockpile is selected in :kbd:`q` mode will launch the + stockpile inventory management screen. + +Usage:: + + enable stocks + stocks show + +Running ``stocks show`` will bring you to the fortress-wide stock management +screen from whereever you are. diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 7c715d956..62c13dcd5 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -1469,10 +1469,10 @@ static command_result stocks_cmd(color_ostream &out, vector & parameter DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back( - PluginCommand( - "stocks", "An improved stocks display screen", - stocks_cmd, false, "Run 'stocks show' open the stocks display screen, or 'stocks version' to query the plugin version.")); + commands.push_back(PluginCommand( + "stocks", + "An improved stocks management screen.", + stocks_cmd)); ViewscreenStocks::reset(); From 464f566928b4d6c883176a18f93620ddb63048a2 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:30:19 -0700 Subject: [PATCH 400/854] update docs for stonesense --- conf.py | 3 +- docs/plugins/stonesense.rst | 81 ++++++++++++++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/conf.py b/conf.py index b01027a6b..08c2e6bd8 100644 --- a/conf.py +++ b/conf.py @@ -183,7 +183,7 @@ def write_tool_docs(): mode=0o755, exist_ok=True) with write_file_if_changed('docs/tools/{}.rst'.format(k[0])) as outfile: outfile.write(header) - if k[0] != 'search' and k[0] != 'stonesense': + if k[0] != 'search': outfile.write(label) outfile.write(include) @@ -315,6 +315,7 @@ exclude_patterns = [ 'docs/pseudoxml/*', 'docs/xml/*', 'scripts/docs/*', + 'plugins/*', ] # The reST default role (used for this markup: `text`) to use for all diff --git a/docs/plugins/stonesense.rst b/docs/plugins/stonesense.rst index b6c154e66..59212444b 100644 --- a/docs/plugins/stonesense.rst +++ b/docs/plugins/stonesense.rst @@ -1,10 +1,79 @@ -.. _plugin-stonesense: - stonesense ========== -An isometric visualizer that runs in a second window. Usage: +Tags: +:dfhack-keybind:`stonesense` +:dfhack-keybind:`ssense` + +A 3D isometric visualizer that runs in a second window. + +Usage: + +``stonesense`` or ``ssense`` + Open the visualiser in a new window. +``ssense overlay`` + Overlay DF window, replacing the map area. + +The viewer window has read-only access to the game, and can follow the game view +or be moved independently. Configuration for stonesense can be set in the +``stonesense/init.txt`` file in your DF game directory. If the window refresh +rate is too low, change ``SEGMENTSIZE_Z`` to ``2`` in this file, and if you are +unable to see the edges of the map with the overlay active, try decreasing the +value for ``SEGMENTSIZE_XY`` -- normal values are ``50`` to ``80``, depending +on your screen resolution. + +If you replace the map section of your DF window with ``ssense overlay``, be +aware that it's not (yet) suitable for use as your only interface. Use DF's +``[PRINT_MODE:2D]`` init option (in ``data/init/init.txt``) for stability. + +.. figure:: ../images/stonesense-roadtruss.jpg + :align: center + :target: http://www.bay12forums.com/smf/index.php?topic=48172.msg3198664#msg3198664 + + The above-ground part of the fortress *Roadtruss*. + +Controls +-------- +Mouse controls are hard-coded and cannot be changed. + +:Left click: Move debug cursor (if available) +:Right click: Recenter screen +:Scrollwheel: Move up and down +:Ctrl-Scroll: Increase/decrease Z depth shown + +Follow mode makes the Stonesense view follow the location of the DF +window. The offset can be adjusted by holding :kbd:`Ctrl` while using the +keyboard window movement keys. When you turn on cursor follow mode, the +Stonesense debug cursor will follow the DF cursor when the latter exists. + +You can take screenshots with :kbd:`F5`, larger screenshots with +:kbd:`Ctrl`:kbd:`F5`, and screenshot the whole map at full resolution with +:kbd:`Ctrl`:kbd:`Shift`:kbd:`F5`. Screenshots are saved to the DF directory. +Note that feedback is printed to the DFHack console, and you may need +to zoom out before taking very large screenshots. + +See ``stonesense/keybinds.txt`` to learn or set keybindings, including +zooming, changing the dimensions of the rendered area, toggling various +views, fog, and rotation. Here's the important section: + +.. include:: ../../plugins/stonesense/resources/keybinds.txt + :literal: + :end-before: VALID ACTIONS: + +Known Issues +------------ +If Stonesense gives an error saying that it can't load +:file:`creatures/large_256/*.png`, your video card cannot handle the high +detail sprites used. Either open :file:`creatures/init.txt` and remove the +line containing that folder, or :dffd:`use these smaller sprites <6096>`. -:stonesense: Open the visualiser in a new window. Alias ``ssense``. -:ssense overlay: Overlay DF window, replacing the map area. +Stonesense requires working graphics acceleration, and we recommend +at least a dual core CPU to avoid slowing down your game of DF. -For more information, see `the full Stonesense README `. +Useful links +------------ +- :forums:`Official Stonesense thread <106497>` for feedback, + questions, requests or bug reports +- :forums:`Screenshots thread <48172>` +- :wiki:`Main wiki page ` +- :wiki:`How to add content ` +- `Stonesense on Github `_ From 3d0eab1a9b20f460d6fa26e2ce4d4988d243e3a3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:30:33 -0700 Subject: [PATCH 401/854] update docs for strangemood --- docs/plugins/strangemood.rst | 56 +++++++++++++++++++++++++----------- plugins/strangemood.cpp | 14 +++------ 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/docs/plugins/strangemood.rst b/docs/plugins/strangemood.rst index 0d578c363..d6065f8eb 100644 --- a/docs/plugins/strangemood.rst +++ b/docs/plugins/strangemood.rst @@ -1,19 +1,41 @@ strangemood =========== -Creates a strange mood job the same way the game itself normally does it. - -Options: - -:-force: Ignore normal strange mood preconditions (no recent mood, minimum - moodable population, artifact limit not reached). -:-unit: Make the strange mood strike the selected unit instead of picking - one randomly. Unit eligibility is still enforced. -:-type : Force the mood to be of a particular type instead of choosing randomly based on happiness. - Valid values for T are "fey", "secretive", "possessed", "fell", and "macabre". -:-skill S: Force the mood to use a specific skill instead of choosing the highest moodable skill. - Valid values are "miner", "carpenter", "engraver", "mason", "tanner", "weaver", - "clothier", "weaponsmith", "armorsmith", "metalsmith", "gemcutter", "gemsetter", - "woodcrafter", "stonecrafter", "metalcrafter", "glassmaker", "leatherworker", - "bonecarver", "bowyer", and "mechanic". - -Known limitations: if the selected unit is currently performing a job, the mood will not be started. +Tags: +:dfhack-keybind:`strangemood` + +Triggers a strange mood. + +Usage:: + + stangemood [] + +Examples +-------- + +``strangemood -force -unit -type secretive -skill armorsmith`` + Trigger a strange mood for the selected unit that will cause them to become + a legendary armorsmith. + +Options +------- + +``-force`` + Ignore normal strange mood preconditions (no recent mood, minimum moodable + population, artifact limit not reached, etc.). +``-unit`` + Make the strange mood strike the selected unit instead of picking one + randomly. Unit eligibility is still enforced (unless ``-force`` is also + specified). +``-type `` + Force the mood to be of a particular type instead of choosing randomly based + on happiness. Valid values are "fey", "secretive", "possessed", "fell", and + "macabre". +``-skill `` + Force the mood to use a specific skill instead of choosing the highest + moodable skill. Valid values are "miner", "carpenter", "engraver", "mason", + "tanner", "weaver", "clothier", "weaponsmith", "armorsmith", "metalsmith", + "gemcutter", "gemsetter", "woodcrafter", "stonecrafter", "metalcrafter", + "glassmaker", "leatherworker", "bonecarver", "bowyer", and "mechanic". + +Known limitations: if the selected unit is currently performing a job, the mood +will not be triggered. diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index de7fa426d..a5b8e74a4 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -1234,16 +1234,10 @@ command_result df_strangemood (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("strangemood", "Force a strange mood to happen.", df_strangemood, false, - "Options:\n" - " -force - Ignore standard mood preconditions.\n" - " -unit - Use the selected unit instead of picking one randomly.\n" - " -type - Force the mood to be of a specific type.\n" - " Valid types: fey, secretive, possessed, fell, macabre\n" - " -skill - Force the mood to use a specific skill.\n" - " Skill name must be lowercase and without spaces.\n" - " Example: miner, gemcutter, metalcrafter, bonecarver, mason\n" - )); + commands.push_back(PluginCommand( + "strangemood", + "Trigger a strange mood.", + df_strangemood)); rng.init(); return CR_OK; From b240748684a953d9279667c019a100ce0f698e31 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:30:46 -0700 Subject: [PATCH 402/854] update docs for tailor --- docs/plugins/tailor.rst | 40 +++++++++++++++++++++++++++++++++------- plugins/tailor.cpp | 28 +++++----------------------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/docs/plugins/tailor.rst b/docs/plugins/tailor.rst index ee1567fdb..19574bc3e 100644 --- a/docs/plugins/tailor.rst +++ b/docs/plugins/tailor.rst @@ -1,11 +1,37 @@ tailor ====== +Tags: +:dfhack-keybind:`tailor` -Whenever the bookkeeper updates stockpile records, this plugin will scan every unit in the fort, -count up the number that are worn, and then order enough more made to replace all worn items. -If there are enough replacement items in inventory to replace all worn items, the units wearing them -will have the worn items confiscated (in the same manner as the `cleanowned` plugin) so that they'll -reeequip with replacement items. +Automatically keep your dwarves in fresh clothing. 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. -Use the `enable` and `disable ` commands to toggle this plugin's status, or run -``tailor status`` to check its current status. +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:: + + enable tailor + tailor status + tailor materials [ ...] + +By default, ``tailor`` will prefer using materials in this order:: + + silk cloth yarn leather + +but you can use the ``tailor materials`` command to restrict which materials +are used, and in what order. + +Example +------- + +``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 + other uses, like making armor. diff --git a/plugins/tailor.cpp b/plugins/tailor.cpp index a48bea2c6..bd11988d0 100644 --- a/plugins/tailor.cpp +++ b/plugins/tailor.cpp @@ -41,26 +41,6 @@ DFHACK_PLUGIN_IS_ENABLED(enabled); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(ui); -const char* tagline = "Allow the bookkeeper to queue jobs to keep dwarfs in adequate clothing."; -const char* usage = ( - " tailor enable\n" - " Enable the plugin.\n" - " tailor disable\n" - " Disable the plugin.\n" - " tailor status\n" - " Display plugin status\n" - " tailor materials ...\n" - " for example: tailor materials silk cloth yarn leather\n" - " Set allowed material list to the specified list.\n" - " The example sets the list to silk, cloth, yarn, leather, in that order, which is the default.\n" - "\n" - "Whenever the bookkeeper updates stockpile records, this plugin will scan every unit in the fort,\n" - "count up the number that are worn, and then order enough more made to replace all worn items.\n" - "If there are enough replacement items in inventory to replace all worn items, the units wearing them\n" - "will have the worn items confiscated (in the same manner as the _cleanowned_ plugin) so that they'll\n" - "reeequip with replacement items.\n" - ); - class Tailor { // ARMOR, SHOES, HELM, GLOVES, PANTS @@ -581,8 +561,7 @@ static command_result tailor_cmd(color_ostream& out, vector & parameters } else if (parameters.size() == 1 && (parameters[0] == "usage" || parameters[0] == "help" || parameters[0] == "?")) { - out.print("%s: %s\nUsage:\n%s", plugin_name, tagline, usage); - return CR_OK; + return CR_WRONG_USAGE; } else if (parameters.size() == 1 && parameters[0] == "test") { @@ -649,7 +628,10 @@ DFhackCExport command_result plugin_init(color_ostream& out, std::vector Date: Sun, 31 Jul 2022 13:30:58 -0700 Subject: [PATCH 403/854] update docs for tiletypes --- docs/plugins/tiletypes.rst | 253 +++++++++++++++++++++---------------- plugins/tiletypes.cpp | 10 +- 2 files changed, 146 insertions(+), 117 deletions(-) diff --git a/docs/plugins/tiletypes.rst b/docs/plugins/tiletypes.rst index 74af00240..cbdbd6cc5 100644 --- a/docs/plugins/tiletypes.rst +++ b/docs/plugins/tiletypes.rst @@ -3,116 +3,149 @@ tiletypes ========= -Can be used for painting map tiles and is an interactive command, much like -`liquids`. Some properties of existing tiles can be looked up with `probe`. If -something goes wrong, `fixveins` may help. - -The tool works with two set of options and a brush. The brush determines which -tiles will be processed. First set of options is the filter, which can exclude -some of the tiles from the brush by looking at the tile properties. The second -set of options is the paint - this determines how the selected tiles are -changed. - -Both paint and filter can have many different properties including things like -general shape (WALL, FLOOR, etc.), general material (SOIL, STONE, MINERAL, -etc.), state of 'designated', 'hidden' and 'light' flags. - -The properties of filter and paint can be partially defined. This means that -you can for example turn all stone fortifications into floors, preserving the -material:: - - filter material STONE - filter shape FORTIFICATION - paint shape FLOOR - -Or turn mineral vein floors back into walls:: - - filter shape FLOOR - filter material MINERAL - paint shape WALL - -The tool also allows tweaking some tile flags:: - - paint hidden 1 - paint hidden 0 - -This will hide previously revealed tiles (or show hidden with the 0 option). - -More recently, the tool supports changing the base material of the tile to -an arbitrary stone from the raws, by creating new veins as required. Note -that this mode paints under ice and constructions, instead of overwriting -them. To enable, use:: - - paint stone MICROCLINE - -This mode is incompatible with the regular ``material`` setting, so changing -it cancels the specific stone selection:: - - paint material ANY - -Since different vein types have different drop rates, it is possible to choose -which one to use in painting:: - - paint veintype CLUSTER_SMALL - -When the chosen type is ``CLUSTER`` (the default), the tool may automatically -choose to use layer stone or lava stone instead of veins if its material matches -the desired one. - -Any paint or filter option (or the entire paint or filter) can be disabled entirely by using the ANY keyword:: - - paint hidden ANY - paint shape ANY - filter material any - filter shape any - filter any - -You can use several different brushes for painting tiles: - -:point: a single tile -:range: a rectangular range -:column: a column ranging from current cursor to the first solid tile above -:block: a DF map block - 16x16 tiles, in a regular grid - -Example:: - - range 10 10 1 - -This will change the brush to a rectangle spanning 10x10 tiles on one z-level. -The range starts at the position of the cursor and goes to the east, south and -up. - -For more details, use ``tiletypes help``. - -tiletypes-command ------------------ -Runs tiletypes commands, separated by ``;``. This makes it possible to change -tiletypes modes from a hotkey or via dfhack-run. - -Example:: - - tiletypes-command p any ; p s wall ; p sp normal - -This resets the paint filter to unsmoothed walls. - -tiletypes-here-point --------------------- -Apply the current tiletypes options at the in-game cursor position to a single -tile. Can be used from a hotkey. - -This command supports the same options as `tiletypes-here` above. - -tiletypes-here --------------- -Apply the current tiletypes options at the in-game cursor position, including -the brush. Can be used from a hotkey. - -Options: - -:``-c``, ``--cursor ,,``: +Tags: +:dfhack-keybind:`tiletypes` +:dfhack-keybind:`tiletypes-command` +:dfhack-keybind:`tiletypes-here` +:dfhack-keybind:`tiletypes-here-point` + +Paints tiles of specified types onto the map. You can use the `probe` command +to discover properties of existing tiles that you'd like to copy. If you +accidentally paint over a vein that you want back, `fixveins` may help. + +The tool works with a brush, a filter, and a paint specification. The brush +determines the shape of the area to affect, the filter selects which tiles to +affect, and the paint specification determines how to affect those tiles. + +Both paint and filter can have many different properties, like general shape +(WALL, FLOOR, etc.), general material (SOIL, STONE, MINERAL, etc.), specific +materials (MICROCLINE, MARBLE, etc.), state of 'designated', 'hidden', and +'light' flags, and many others. + +Usage: + +``tiletypes`` + Start the interactive terminal prompt where you can iteratively modify + the brush, filter, and paint specification and get help on syntax + elements. When in the interactive prompt, type ``quit`` to get out. +``tiletypes-command [; ...]`` + Run ``tiletypes`` commands from outside the interactive prompt. You can + use this form from hotkeys or `dfhack-run` to set specific tiletypes + properties. You can run multiple commands on one line by separating them + with :literal:`\ ; \ ` -- that's a semicolon with a space on either side. + See the Commands_ section below for an overview of commands you can run. +``tiletypes-here []`` + Apply the current options set in ``tiletypes`` and/or ``tiletypes-command`` + at the in-game cursor position, including the brush. Can be used from a + hotkey. +``tiletypes-here-point []`` + Apply the current options set in ``tiletypes`` and/or ``tiletypes-command`` + at the in-game cursor position to a single tile (ignoring brush settings). + Can be used from a hotkey. + +Examples +-------- + +``tiletypes-command filter material STONE ; f shape WALL ; paint shape FLOOR`` + Turn all stone walls into floors, preserving the material. +``tiletypes-command p any ; p s wall ; p sp normal`` + Clear the paint specificaiton and set it to unsmoothed walls. +``tiletypes-command f any ; p stone marble ; p sh wall ; p sp normal ; r 10 10`` + Prepare to paint a 10x10 area of marble walls, ready for harvesting for + flux. +``tiletypes-command f any ; f designated 1 ; p any ; p hidden 0 ; block ; run`` + Set the filter to match designated tiles, the paint specification to unhide + them, and the brush to cover all tiles in the current block. Then run itThis is useful + for unhiding tiles you wish to dig out of an aquifer so the game doesn't + pause and undesignate adjacent tiles every time a new damp tile is + "discovered". + +Options +------- + +``-c``, ``--cursor ,,`` Use the specified map coordinates instead of the current cursor position. If this option is specified, then an active game map cursor is not necessary. -:``-h``, ``--help``: - Show command help text. -:``-q``, ``--quiet``: +``-q``, ``--quiet`` Suppress non-error status output. + +Commands +-------- + +Commands can set the brush or modify the filter or paint options. When at the +interactive ``tiletypes>`` prompt, the command ``run`` (or hitting enter on an +empty line) will apply the current filter and paint specification with the +current brush at the current cursor position. The command ``quit`` will exit. + +Brush commands +`````````````` + +``p``, ``point`` + Use the point brush. +``r``, ``range []`` + Use the range brush with the specified width, height, and depth. If not + specified, depth is 1, meaning just the current z-level. The range starts at + the position of the cursor and goes to the east, south and up (towards the + sky). +``block`` + Use the block brush, which includes all tiles in the 16x16 block that + includes the cursor. +``column`` + Use the column brush, which ranges from the current cursor position to the + first solid tile above it. This is useful for filling the empty space in a + cavern. + +Filter and paint commands +````````````````````````` + +The general forms for modifying the filter or paint specification are: + +``f``, ``filter `` + Modify the filter. +``p``, ``paint `` + Modify the paint specification. + +The options identify the property of the tile and the value of that property: + +``any`` + Reset to default (no filter/paint). +``s``, ``sh``, ``shape `` + Tile shape information. Run ``:lua @df.tiletype_shape`` to see valid shapes, + or use a shape of ``any`` to clear the current setting. +``m``, ``mat``, ``material `` + Tile material information. Run ``:lua @df.tiletype_material`` to see valid + materials, or use a material of ``any`` to clear the current setting. +``sp``, ``special `` + Tile special information. Run ``:lua @df.tiletype_special`` to see valid + special values, or use a special value of ``any`` to clear the current + setting. +``v``, ``var``, ``variant `` + Tile variant information. Run ``:lua @df.tiletype_variant`` to see valid + variant values, or use a variant value of ``any`` to clear the current + setting. +``a``, ``all [] [] [] []`` + Set values for any or all of shape, material, special, and/or variant, in + any order. +``d``, ``designated 0|1`` + Only useful for the filter, since you can't "paint" designations. +``h``, ``hidden 0|1`` + Whether a tile is hidden. A value of ``0`` means "revealed". +``l``, ``light 0|1`` + Whether a tile is marked as "Light". A value of ``0`` means "dark". +``st``, ``subterranean 0|1`` + Whether a tile is marked as "Subterranean". +``sv``, ``skyview 0|1`` + Whether a tile is marked as "Outside". A value of ``0`` means "inside". +``aqua``, ``aquifer 0|1`` + Whether a tile is marked as an aquifer. +``stone `` + Set a particular type of stone, creating veins as required. To see a list of + valid stone types, run: ``:lua for _,mat in ipairs(df.global.world.raws.inorganics) do if mat.material.flags.IS_STONE and not mat.material.flags.NO_STONE_STOCKPILE then print(mat.id) end end`` + Note that this command paints under ice and constructions, instead of + overwriting them. Also note that specifying a specific ``stone`` will cancel + out anything you have specified for ``material``, and vice-versa. +``veintype `` + Set a particular vein type for the ``stone`` option to take advantage of the + different boulder drop rates. To see valid vein types, run + ``:lua @df.inclusion_type``, or use vein type ``CLUSTER`` to reset to the + default. diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index b8bbc8d04..f3773b0fe 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -85,10 +85,10 @@ command_result df_tiletypes_here_point (color_ostream &out, vector & pa DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { tiletypes_hist.load(HISTORY_FILE); - commands.push_back(PluginCommand("tiletypes", "Paint map tiles freely, similar to liquids.", df_tiletypes, true)); + commands.push_back(PluginCommand("tiletypes", "Paints tiles of specified types onto the map.", df_tiletypes, true)); commands.push_back(PluginCommand("tiletypes-command", "Run tiletypes commands (seperated by ' ; ')", df_tiletypes_command)); commands.push_back(PluginCommand("tiletypes-here", "Repeat tiletypes command at cursor (with brush)", df_tiletypes_here)); - commands.push_back(PluginCommand("tiletypes-here-point", "Repeat tiletypes command at cursor (without brush)", df_tiletypes_here_point)); + commands.push_back(PluginCommand("tiletypes-here-point", "Repeat tiletypes command at cursor (with single tile brush)", df_tiletypes_here_point)); return CR_OK; } @@ -1000,11 +1000,7 @@ command_result df_tiletypes (color_ostream &out_, vector & parameters) { if(parameters[i] == "help" || parameters[i] == "?") { - out_.print("This tool allows painting tiles types with a brush, using an optional filter.\n" - "The tool is interactive, similarly to the liquids tool.\n" - "Further help is available inside.\n" - ); - return CR_OK; + return CR_WRONG_USAGE; } } From 2362d60a3aa9d200a234d5a6c2d330eedc1b9a00 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:31:15 -0700 Subject: [PATCH 404/854] update docs for title-folder and title-version --- docs/plugins/title-folder.rst | 8 +++++++- docs/plugins/title-version.rst | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/plugins/title-folder.rst b/docs/plugins/title-folder.rst index 4e1ef40ec..3156a9e1c 100644 --- a/docs/plugins/title-folder.rst +++ b/docs/plugins/title-folder.rst @@ -1,3 +1,9 @@ title-folder ============= -Displays the DF folder name in the window title bar when enabled. +Tags: + +Displays the DF folder name in the window title bar. + +Usage:: + + enable title-folder diff --git a/docs/plugins/title-version.rst b/docs/plugins/title-version.rst index aed7a02e0..267179f14 100644 --- a/docs/plugins/title-version.rst +++ b/docs/plugins/title-version.rst @@ -1,3 +1,9 @@ title-version ============= -Displays the DFHack version on DF's title screen when enabled. +Tags: + +Displays the DFHack version on DF's title screen. + +Usage:: + + enable title-version From a6bdd9e3dc20fe0b6d1b9644e3b9eb5118df0b63 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:31:26 -0700 Subject: [PATCH 405/854] update docs for trackstop --- docs/plugins/trackstop.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/plugins/trackstop.rst b/docs/plugins/trackstop.rst index b012df34d..72466a1bf 100644 --- a/docs/plugins/trackstop.rst +++ b/docs/plugins/trackstop.rst @@ -1,5 +1,12 @@ trackstop ========= -Adds a :kbd:`q` menu for track stops, which is completely blank by default. +Tags: + +Adds dynamic configuration options for track stops. When enabled, this plugin +adds a :kbd:`q` menu for track stops, which is completely blank in vanilla DF. This allows you to view and/or change the track stop's friction and dump direction settings, using the keybindings from the track stop building interface. + +Usage:: + + enable trackstop From a5a57a86312352e150f40dfaae66545971ee933e Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:31:40 -0700 Subject: [PATCH 406/854] update docs for tubefill --- docs/plugins/tubefill.rst | 15 +++++++++------ plugins/tubefill.cpp | 7 ++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/plugins/tubefill.rst b/docs/plugins/tubefill.rst index 99c0b76bd..15a9ba2f2 100644 --- a/docs/plugins/tubefill.rst +++ b/docs/plugins/tubefill.rst @@ -1,11 +1,14 @@ tubefill ======== -Fills all the adamantine veins again. Veins that were hollow will be left -alone. +Tags: +:dfhack-keybind:`tubefill` -Options: +Replentishes mined-out adamantine. Veins that were hollow will be left alone. -:hollow: fill in naturally hollow veins too +Usage:: -Beware that filling in hollow veins will trigger a demon invasion on top of -your miner when you dig into the region that used to be hollow. + tubefill [hollow] + +Specify ``hollow`` to fill in naturally hollow veins too, but be aware that this +will trigger a demon invasion on top of your miner when you dig into the region +that used to be hollow. You have been warned! diff --git a/plugins/tubefill.cpp b/plugins/tubefill.cpp index 1803ed520..f86e77c76 100644 --- a/plugins/tubefill.cpp +++ b/plugins/tubefill.cpp @@ -40,9 +40,10 @@ command_result tubefill(color_ostream &out, std::vector & params); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("tubefill","Fill in all the adamantine tubes again.",tubefill, false, - "Replenishes mined out adamantine but does not fill hollow adamantine tubes.\n" - "Specify 'hollow' to fill hollow tubes, but beware glitchy HFS spawns.\n")); + commands.push_back(PluginCommand( + "tubefill", + "Replentishes mined-out adamantine.", + tubefill)); return CR_OK; } From e4c5b146701803eede95af8c2f62a1193ba2a32a Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:31:54 -0700 Subject: [PATCH 407/854] update docs for tweak --- docs/plugins/tweak.rst | 208 +++++++++++------- plugins/tweak/tweak.cpp | 468 +--------------------------------------- 2 files changed, 130 insertions(+), 546 deletions(-) diff --git a/docs/plugins/tweak.rst b/docs/plugins/tweak.rst index 4ce7b6d38..56d465f78 100644 --- a/docs/plugins/tweak.rst +++ b/docs/plugins/tweak.rst @@ -1,91 +1,137 @@ tweak ===== +Tags: +:dfhack-keybind:`tweak` + Contains various tweaks for minor bugs. -One-shot subcommands: +Usage:: + + tweak [disable] + +Run the ``tweak`` command to run the tweak or enable its effects. For tweaks +that have persistent effects, append the ``disable`` keyword to disable them. + +One-shot commands: -:clear-missing: Remove the missing status from the selected unit. - This allows engraving slabs for ghostly, but not yet - found, creatures. -:clear-ghostly: Remove the ghostly status from the selected unit and mark - it as dead. This allows getting rid of bugged ghosts - which do not show up in the engraving slab menu at all, - even after using clear-missing. It works, but is - potentially very dangerous - so use with care. Probably - (almost certainly) it does not have the same effects like - a proper burial. You've been warned. -:fixmigrant: Remove the resident/merchant flag from the selected unit. - Intended to fix bugged migrants/traders who stay at the - map edge and don't enter your fort. Only works for - dwarves (or generally the player's race in modded games). - Do NOT abuse this for 'real' caravan merchants (if you - really want to kidnap them, use 'tweak makeown' instead, - otherwise they will have their clothes set to forbidden etc). -:makeown: Force selected unit to become a member of your fort. - Can be abused to grab caravan merchants and escorts, even if - they don't belong to the player's race. Foreign sentients - (humans, elves) can be put to work, but you can't assign rooms - to them and they don't show up in DwarfTherapist because the - game treats them like pets. Grabbing draft animals from - a caravan can result in weirdness (animals go insane or berserk - and are not flagged as tame), but you are allowed to mark them - for slaughter. Grabbing wagons results in some funny spam, then - they are scuttled. +``clear-missing`` + Remove the missing status from the selected unit. This allows engraving + slabs for ghostly, but not yet found, creatures. +``clear-ghostly`` + Remove the ghostly status from the selected unit and mark it as dead. This + allows getting rid of bugged ghosts which do not show up in the engraving + slab menu at all, even after using ``clear-missing``. It works, but is + potentially very dangerous - so use with care. Probably (almost certainly) + it does not have the same effects like a proper burial. You've been warned. +``fixmigrant`` + Remove the resident/merchant flag from the selected unit. Intended to fix + bugged migrants/traders who stay at the map edge and don't enter your fort. + Only works for dwarves (or generally the player's race in modded games). + Do NOT abuse this for 'real' caravan merchants (if you really want to kidnap + them, use ``tweak makeown`` instead, otherwise they will have their clothes + set to forbidden). +``makeown`` + Force selected unit to become a member of your fort. Can be abused to grab + caravan merchants and escorts, even if they don't belong to the player's + race. Foreign sentients (humans, elves) can be put to work, but you can't + assign rooms to them and they don't show up in labor management programs + (like `manipulator` or Dwarf Therapist) because the game treats them like + pets. Grabbing draft animals from a caravan can result in weirdness + (animals go insane or berserk and are not flagged as tame), but you are + allowed to mark them for slaughter. Grabbing wagons results in some funny + spam, then they are scuttled. -Subcommands that persist until disabled or DF quits: +Commands that persist until disabled or DF quits: -.. comment: sort these alphabetically +.. comment: please sort these alphabetically -:adamantine-cloth-wear: Prevents adamantine clothing from wearing out while being worn (:bug:`6481`). -:advmode-contained: Works around :bug:`6202`, custom reactions with container inputs - in advmode. The issue is that the screen tries to force you to select - the contents separately from the container. This forcefully skips child - reagents. -:block-labors: Prevents labors that can't be used from being toggled -:burrow-name-cancel: Implements the "back" option when renaming a burrow, - which currently does nothing (:bug:`1518`) -:cage-butcher: Adds an option to butcher units when viewing cages with :kbd:`q` -:civ-view-agreement: Fixes overlapping text on the "view agreement" screen -:condition-material: Fixes a crash in the work order contition material list (:bug:`9905`). -:craft-age-wear: Fixes the behavior of crafted items wearing out over time (:bug:`6003`). - With this tweak, items made from cloth and leather will gain a level of - wear every 20 years. -:do-job-now: Adds a job priority toggle to the jobs list -:embark-profile-name: Allows the use of lowercase letters when saving embark profiles -:eggs-fertile: Displays a fertility indicator on nestboxes -:farm-plot-select: Adds "Select all" and "Deselect all" options to farm plot menus -:fast-heat: Further improves temperature update performance by ensuring that 1 degree - of item temperature is crossed in no more than specified number of frames - when updating from the environment temperature. This reduces the time it - takes for stable-temp to stop updates again when equilibrium is disturbed. -:fast-trade: Makes Shift-Down in the Move Goods to Depot and Trade screens select - the current item (fully, in case of a stack), and scroll down one line. -:fps-min: Fixes the in-game minimum FPS setting -:hide-priority: Adds an option to hide designation priority indicators -:hotkey-clear: Adds an option to clear currently-bound hotkeys (in the :kbd:`H` menu) -:import-priority-category: - Allows changing the priority of all goods in a - category when discussing an import agreement with the liaison -:kitchen-prefs-all: Adds an option to toggle cook/brew for all visible items in kitchen preferences -:kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences -:kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs (:bug:`9000`) -:max-wheelbarrow: Allows assigning more than 3 wheelbarrows to a stockpile -:military-color-assigned: - Color squad candidates already assigned to other squads in yellow/green - to make them stand out more in the list. +``adamantine-cloth-wear`` + Prevents adamantine clothing from wearing out while being worn + (:bug:`6481`). +``advmode-contained`` + Fixes custom reactions with container inputs in advmode + (:bug:`6202`) in advmode. The issue is that the screen tries to force you to + select the contents separately from the container. This forcefully skips + child reagents. +``block-labors`` + Prevents labors that can't be used from being toggled. +``burrow-name-cancel`` + Implements the "back" option when renaming a burrow, which currently does + nothing in vanilla DF (:bug:`1518`). +``cage-butcher`` + Adds an option to butcher units when viewing cages with :kbd:`q`. +``civ-view-agreement`` + Fixes overlapping text on the "view agreement" screen. +``condition-material`` + Fixes a crash in the work order contition material list (:bug:`9905`). +``craft-age-wear`` + Fixes crafted items not wearing out over time (:bug:`6003`). With this + tweak, items made from cloth and leather will gain a level of wear every 20 + years. +``do-job-now`` + Adds a job priority toggle to the jobs list. +``embark-profile-name`` + Allows the use of lowercase letters when saving embark profiles. +``eggs-fertile`` + Displays a fertility indicator on nestboxes. +``farm-plot-select`` + Adds "Select all" and "Deselect all" options to farm plot menus. +``fast-heat`` + Improves temperature update performance by ensuring that 1 degree of item + temperature is crossed in no more than specified number of frames when + updating from the environment temperature. This reduces the time it takes + for ``tweak stable-temp`` to stop updates again when equilibrium is + disturbed. +``fast-trade`` + Makes Shift-Down in the Move Goods to Depot and Trade screens toggle the + current item (fully, in case of a stack), and scroll down one line. Shift-Up + undoes the last Shift-Down by scrolling up one line and then toggle the item. +``fps-min`` + Fixes the in-game minimum FPS setting (:bug:`6277`). +``hide-priority`` + Adds an option to hide designation priority indicators. +``hotkey-clear`` + Adds an option to clear currently-bound hotkeys (in the :kbd:`H` menu). +``import-priority-category`` + When meeting with a liaison, makes Shift+Left/Right arrow adjust all items + in category when discussing an import agreement with the liaison. +``kitchen-prefs-all`` + Adds an option to toggle cook/brew for all visible items in kitchen + preferences. +``kitchen-prefs-color`` + Changes color of enabled items to green in kitchen preferences. +``kitchen-prefs-empty`` + Fixes a layout issue with empty kitchen tabs (:bug:`9000`). +``max-wheelbarrow`` + Allows assigning more than 3 wheelbarrows to a stockpile. +``military-color-assigned`` + Color squad candidates already assigned to other squads in yellow/green to + make them stand out more in the list. - .. image:: ../images/tweak-mil-color.png + .. image:``../images/tweak-mil-color.png -:military-stable-assign: - Preserve list order and cursor position when assigning to squad, - i.e. stop the rightmost list of the Positions page of the military - screen from constantly resetting to the top. -:nestbox-color: Fixes the color of built nestboxes -:partial-items: Displays percentages on partially-consumed items such as hospital cloth -:reaction-gloves: Fixes reactions to produce gloves in sets with correct handedness (:bug:`6273`) -:shift-8-scroll: Gives Shift-8 (or :kbd:`*`) priority when scrolling menus, instead of scrolling the map -:stable-cursor: Saves the exact cursor position between t/q/k/d/b/etc menus of fortress mode, if the - map view is near enough to its previous position. -:stone-status-all: Adds an option to toggle the economic status of all stones -:title-start-rename: Adds a safe rename option to the title screen "Start Playing" menu -:tradereq-pet-gender: Displays pet genders on the trade request screen +``military-stable-assign`` + Preserve list order and cursor position when assigning to squad, i.e. stop + the rightmost list of the Positions page of the military screen from + constantly resetting to the top. +``nestbox-color`` + Makes built nestboxes use the color of their material. +``partial-items`` + Displays percentages on partially-consumed items such as hospital cloth. +``pausing-fps-counter`` + Replace fortress mode FPS counter with one that stops counting when paused. +``reaction-gloves`` + Fixes reactions to produce gloves in sets with correct handedness + (:bug:`6273`). +``shift-8-scroll`` + Gives Shift-8 (or :kbd:`*`) priority when scrolling menus, instead of + scrolling the map. +``stable-cursor`` + Saves the exact cursor position between t/q/k/d/b/etc menus of fortress + mode, if the map view is near enough to its previous position. +``stone-status-all`` + Adds an option to toggle the economic status of all stones. +``title-start-rename`` + Adds a safe rename option to the title screen "Start Playing" menu. +``tradereq-pet-gender`` + Displays pet genders on the trade request screen. diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 38ece6970..6d2a46fa8 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -23,7 +23,6 @@ #include "../uicommon.h" #include "df/ui.h" #include "df/world.h" -#include "df/squad.h" #include "df/unit.h" #include "df/unit_soul.h" #include "df/historical_entity.h" @@ -37,7 +36,6 @@ #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_kitchenprefst.h" #include "df/viewscreen_layer_unit_actionst.h" -#include "df/squad_order_trainst.h" #include "df/ui_build_selector.h" #include "df/ui_sidebar_menus.h" #include "df/building_trapst.h" @@ -67,17 +65,10 @@ #include "df/viewscreen_layer_assigntradest.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_layer_militaryst.h" -#include "df/squad_position.h" #include "df/job.h" #include "df/general_ref_building_holderst.h" #include "df/unit_health_info.h" #include "df/caste_body_info.h" -#include "df/activity_entry.h" -#include "df/activity_event_combat_trainingst.h" -#include "df/activity_event_individual_skill_drillst.h" -#include "df/activity_event_skill_demonstrationst.h" -#include "df/activity_event_sparringst.h" -//#include "df/building_hivest.h" #include #include @@ -162,113 +153,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector \n" - " Further improves temperature updates by ensuring that 1 degree of\n" - " item temperature is crossed in no more than specified number of frames\n" - " when updating from the environment temperature. Use 0 to disable.\n" - " tweak fast-trade [disable]\n" - " Makes Shift-Enter in the Move Goods to Depot and Trade screens select\n" - " the current item (fully, in case of a stack), and scroll down one line.\n" - " tweak fps-min [disable]\n" - " Fixes the in-game minimum FPS setting (bug 6277)\n" - " tweak hide-priority [disable]\n" - " Adds an option to hide designation priority indicators\n" - " tweak hotkey-clear [disable] \n" - " Adds an option to clear currently-bound hotkeys\n" - " tweak import-priority-category [disable]\n" - " When meeting with a liaison, makes Shift+Left/Right arrow adjust\n" - " the priority of an entire category of imports.\n" - " tweak kitchen-prefs-all [disable]\n" - " Adds an option to toggle cook/brew for all visible items in\n" - " kitchen preferences\n" - " tweak kitchen-prefs-color [disable]\n" - " Changes color of enabled items to green in kitchen preferences\n" - " tweak kitchen-prefs-empty [disable]\n" - " Fixes a layout issue with empty kitchen tabs (bug 9000)\n" - " tweak max-wheelbarrow [disable]\n" - " Allows assigning more than 3 wheelbarrows to a stockpile\n" - " tweak nestbox-color [disable]\n" - " Makes built nestboxes use the color of their material\n" - " tweak military-color-assigned [disable]\n" - " Color squad candidates already assigned to other squads in brown/green\n" - " to make them stand out more in the list.\n" - " tweak military-stable-assign [disable]\n" - " Preserve list order and cursor position when assigning to squad,\n" - " i.e. stop the rightmost list of the Positions page of the military\n" - " screen from constantly jumping to the top.\n" - " tweak partial-items [disable]\n" - " Displays percentages on partially-consumed items such as hospital cloth\n" - " tweak pausing-fps-counter [disable]\n" - " Replace fortress mode FPS counter with one that stops counting \n" - " when paused.\n" - " tweak reaction-gloves [disable]\n" - " Changes custom reactions to produce gloves in sets with correct handedness\n" - " tweak shift-8-scroll [disable]\n" - " Gives Shift+8 (or *) priority when scrolling menus, instead of \n" - " scrolling the map\n" - " tweak stone-status-all [disable]\n" - " Adds an option to toggle the economic status of all stones\n" - " tweak title-start-rename [disable]\n" - " Adds a safe rename option to the title screen \"Start Playing\" menu\n" - " tweak tradereq-pet-gender [disable]\n" - " Displays the gender of pets in the trade request list\n" - // sort these alphabetically -// " tweak military-training [disable]\n" -// " Speed up melee squad training, removing inverse dependency on unit count.\n" - )); + "tweak", + "Various tweaks for minor bugs.", + tweak)); TWEAK_HOOK("adamantine-cloth-wear", adamantine_cloth_wear_armor_hook, incWearTimer); TWEAK_HOOK("adamantine-cloth-wear", adamantine_cloth_wear_helm_hook, incWearTimer); @@ -438,339 +325,6 @@ command_result fix_clothing_ownership(color_ostream &out, df::unit* unit) return CR_OK; } -static void correct_dimension(df::item_actual *self, int32_t &delta, int32_t dim) -{ - // Zero dimension or remainder? - if (dim <= 0 || self->stack_size <= 1) return; - int rem = delta % dim; - if (rem == 0) return; - // If destroys, pass through - int intv = delta / dim; - if (intv >= self->stack_size) return; - // Subtract int part - delta = rem; - self->stack_size -= intv; - if (self->stack_size <= 1) return; - - // If kills the item or cannot split, round up. - if (!self->flags.bits.in_inventory || !Items::getContainer(self)) - { - delta = dim; - return; - } - - // Otherwise split the stack - color_ostream_proxy out(Core::getInstance().getConsole()); - out.print("fix-dimensions: splitting stack #%d for delta %d.\n", self->id, delta); - - auto copy = self->splitStack(self->stack_size-1, true); - if (copy) copy->categorize(true); -} - -struct dimension_liquid_hook : df::item_liquid_miscst { - typedef df::item_liquid_miscst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) - { - correct_dimension(this, delta, dimension); - return INTERPOSE_NEXT(subtractDimension)(delta); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(dimension_liquid_hook, subtractDimension); - -struct dimension_powder_hook : df::item_powder_miscst { - typedef df::item_powder_miscst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) - { - correct_dimension(this, delta, dimension); - return INTERPOSE_NEXT(subtractDimension)(delta); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(dimension_powder_hook, subtractDimension); - -struct dimension_bar_hook : df::item_barst { - typedef df::item_barst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) - { - correct_dimension(this, delta, dimension); - return INTERPOSE_NEXT(subtractDimension)(delta); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(dimension_bar_hook, subtractDimension); - -struct dimension_thread_hook : df::item_threadst { - typedef df::item_threadst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) - { - correct_dimension(this, delta, dimension); - return INTERPOSE_NEXT(subtractDimension)(delta); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(dimension_thread_hook, subtractDimension); - -struct dimension_cloth_hook : df::item_clothst { - typedef df::item_clothst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) - { - correct_dimension(this, delta, dimension); - return INTERPOSE_NEXT(subtractDimension)(delta); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(dimension_cloth_hook, subtractDimension); - -/* -// Unit updates are executed based on an action divisor variable, -// which is computed from the alive unit count and has range 10-100. -static int adjust_unit_divisor(int value) { - return value*10/DF_GLOBAL_FIELD(ui, unit_action_divisor, 10); -} - -static bool can_spar(df::unit *unit) { - return unit->counters2.exhaustion <= 2000 && // actually 4000, but leave a gap - (unit->status2.limbs_grasp_count > 0 || unit->status2.limbs_grasp_max == 0) && - (!unit->health || (unit->health->flags.whole&0x7FF) == 0) && - (!unit->job.current_job || unit->job.current_job->job_type != job_type::Rest); -} - -static bool has_spar_inventory(df::unit *unit, df::job_skill skill) -{ - using namespace df::enums::job_skill; - - auto type = ENUM_ATTR(job_skill, type, skill); - - if (type == job_skill_class::MilitaryWeapon) - { - for (size_t i = 0; i < unit->inventory.size(); i++) - { - auto item = unit->inventory[i]; - if (item->mode == df::unit_inventory_item::Weapon && - item->item->getMeleeSkill() == skill) - return true; - } - - return false; - } - - switch (skill) { - case THROW: - case RANGED_COMBAT: - return false; - - case SHIELD: - for (size_t i = 0; i < unit->inventory.size(); i++) - { - auto item = unit->inventory[i]; - if (item->mode == df::unit_inventory_item::Weapon && - item->item->getType() == item_type::SHIELD) - return true; - } - return false; - - case ARMOR: - for (size_t i = 0; i < unit->inventory.size(); i++) - { - auto item = unit->inventory[i]; - if (item->mode == df::unit_inventory_item::Worn && - item->item->isArmorNotClothing()) - return true; - } - return false; - - default: - return true; - } -} - -struct military_training_ct_hook : df::activity_event_combat_trainingst { - typedef df::activity_event_combat_trainingst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit)) - { - auto act = df::activity_entry::find(activity_id); - int cur_neid = act ? act->next_event_id : 0; - int cur_oc = organize_counter; - - INTERPOSE_NEXT(process)(unit); - - // Shorten the time it takes to organize stuff, so that in - // reality it remains the same instead of growing proportionally - // to the unit count. - if (organize_counter > cur_oc && organize_counter > 0) - organize_counter = adjust_unit_divisor(organize_counter); - - if (act && act->next_event_id > cur_neid) - { - // New events were added. Check them. - for (size_t i = 0; i < act->events.size(); i++) - { - auto event = act->events[i]; - if (event->flags.bits.dismissed || event->event_id < cur_neid) - continue; - - if (auto sp = strict_virtual_cast(event)) - { - // Sparring has a problem in that all of its participants decrement - // the countdown variable. Fix this by multiplying it by the member count. - sp->countdown = sp->countdown * sp->participants.units.size(); - } - else if (auto sd = strict_virtual_cast(event)) - { - // Adjust initial counter values - sd->train_countdown = adjust_unit_divisor(sd->train_countdown); - sd->wait_countdown = adjust_unit_divisor(sd->wait_countdown); - - // Check if the game selected the most skilled unit as the teacher - auto &units = sd->participants.units; - int maxv = -1, cur_xp = -1, minv = 0; - int best = -1; - size_t spar = 0; - - for (size_t j = 0; j < units.size(); j++) - { - auto unit = df::unit::find(units[j]); - if (!unit) continue; - int xp = Units::getExperience(unit, sd->skill, true); - if (units[j] == sd->unit_id) - cur_xp = xp; - if (j == 0 || xp < minv) - minv = xp; - if (xp > maxv) { - maxv = xp; - best = j; - } - if (can_spar(unit) && has_spar_inventory(unit, sd->skill)) - spar++; - } - -#if 0 - color_ostream_proxy out(Core::getInstance().getConsole()); -#endif - - // If the xp gap is low, sometimes replace with sparring - if ((maxv - minv) < 64*15 && spar == units.size() && - random_int(45) >= 30 + (maxv-minv)/64) - { -#if 0 - out.print("Replacing %s demonstration (xp %d-%d, gap %d) with sparring.\n", - ENUM_KEY_STR(job_skill, sd->skill).c_str(), minv, maxv, maxv-minv); -#endif - - if (auto spar = df::allocate()) - { - spar->event_id = sd->event_id; - spar->activity_id = sd->activity_id; - spar->parent_event_id = sd->parent_event_id; - spar->flags = sd->flags; - spar->participants = sd->participants; - spar->building_id = sd->building_id; - spar->countdown = 300*units.size(); - - delete sd; - act->events[i] = spar; - - continue; - } - } - - // If the teacher has less xp than somebody else, switch - if (best >= 0 && maxv > cur_xp) - { -#if 0 - out.print("Replacing %s teacher %d (%d xp) with %d (%d xp); xp gap %d.\n", - ENUM_KEY_STR(job_skill, sd->skill).c_str(), - sd->unit_id, cur_xp, units[best], maxv, maxv-minv); -#endif - - sd->hist_figure_id = sd->participants.histfigs[best]; - sd->unit_id = units[best]; - } - else - { -#if 0 - out.print("Not changing %s demonstration (xp %d-%d, gap %d).\n", - ENUM_KEY_STR(job_skill, sd->skill).c_str(), - minv, maxv, maxv-minv); -#endif - } - } - } - } - } -}; -*/ - -/* -IMPLEMENT_VMETHOD_INTERPOSE(military_training_ct_hook, process); - -struct military_training_sd_hook : df::activity_event_skill_demonstrationst { - typedef df::activity_event_skill_demonstrationst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit)) - { - int cur_oc = organize_counter; - int cur_tc = train_countdown; - - INTERPOSE_NEXT(process)(unit); - - // Shorten the counters if they changed - if (organize_counter > cur_oc && organize_counter > 0) - organize_counter = adjust_unit_divisor(organize_counter); - if (train_countdown > cur_tc) - train_countdown = adjust_unit_divisor(train_countdown); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(military_training_sd_hook, process); - -template -bool is_done(T *event, df::unit *unit) -{ - return event->flags.bits.dismissed || - binsearch_index(event->participants.units, unit->id) < 0; -} - -struct military_training_sp_hook : df::activity_event_sparringst { - typedef df::activity_event_sparringst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit)) - { - INTERPOSE_NEXT(process)(unit); - - // Since there are no counters to fix, repeat the call - int cnt = (DF_GLOBAL_FIELD(ui, unit_action_divisor, 10)+5) / 10; - for (int i = 1; i < cnt && !is_done(this, unit); i++) - INTERPOSE_NEXT(process)(unit); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(military_training_sp_hook, process); - -struct military_training_id_hook : df::activity_event_individual_skill_drillst { - typedef df::activity_event_individual_skill_drillst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit)) - { - INTERPOSE_NEXT(process)(unit); - - // Since there are no counters to fix, repeat the call - int cnt = (DF_GLOBAL_FIELD(ui, unit_action_divisor, 10)+5) / 10; - for (int i = 1; i < cnt && !is_done(this, unit); i++) - INTERPOSE_NEXT(process)(unit); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(military_training_id_hook, process); -*/ - static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -931,22 +485,6 @@ static command_result tweak(color_ostream &out, vector ¶meters) enable_tweak(cmd, out, parameters); return CR_OK; } - /*else if (cmd == "fix-dimensions") - { - enable_hook(out, INTERPOSE_HOOK(dimension_liquid_hook, subtractDimension), parameters); - enable_hook(out, INTERPOSE_HOOK(dimension_powder_hook, subtractDimension), parameters); - enable_hook(out, INTERPOSE_HOOK(dimension_bar_hook, subtractDimension), parameters); - enable_hook(out, INTERPOSE_HOOK(dimension_thread_hook, subtractDimension), parameters); - enable_hook(out, INTERPOSE_HOOK(dimension_cloth_hook, subtractDimension), parameters); - }*/ -/* - else if (cmd == "military-training") - { - enable_hook(out, INTERPOSE_HOOK(military_training_ct_hook, process), parameters); - enable_hook(out, INTERPOSE_HOOK(military_training_sd_hook, process), parameters); - enable_hook(out, INTERPOSE_HOOK(military_training_sp_hook, process), parameters); - enable_hook(out, INTERPOSE_HOOK(military_training_id_hook, process), parameters); - }*/ else { return enable_tweak(cmd, out, parameters); From 6686f703aeeae31d43c6c548e5860a6bbbf7831c Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 13:32:06 -0700 Subject: [PATCH 408/854] update docs for workNow --- docs/plugins/workNow.rst | 23 +++++++++++++++-------- plugins/workNow.cpp | 14 ++++---------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/plugins/workNow.rst b/docs/plugins/workNow.rst index bacecf612..dda3fea5c 100644 --- a/docs/plugins/workNow.rst +++ b/docs/plugins/workNow.rst @@ -1,12 +1,19 @@ workNow ======= -Don't allow dwarves to idle if any jobs are available. +Tags: +:dfhack-keybind:`workNow` -When workNow is active, every time the game pauses, DF will make dwarves -perform any appropriate available jobs. This includes when you one step -through the game using the pause menu. Usage: +Reduce the time that dwarves idle after completing a job. After finishing a job, +dwarves will wander away for a while before picking up a new job. This plugin +will automatically poke the game to assign dwarves to new tasks. -:workNow: print workNow status -:workNow 0: deactivate workNow -:workNow 1: activate workNow (look for jobs on pause, and only then) -:workNow 2: make dwarves look for jobs whenever a job completes +Usage: + +``workNow`` + Print current plugin status. +``workNow 0`` + Stop monitoring and poking. +``workNow 1`` + Poke the game to assign dwarves to tasks whenever the game is paused. +``workNow 2`` + Poke the game to assign dwarves to tasks whenever a dwarf finishes a job. diff --git a/plugins/workNow.cpp b/plugins/workNow.cpp index ae76b09f3..2286fee7e 100644 --- a/plugins/workNow.cpp +++ b/plugins/workNow.cpp @@ -29,16 +29,10 @@ DFhackCExport command_result plugin_init(color_ostream& out, std::vector Date: Sun, 31 Jul 2022 13:38:25 -0700 Subject: [PATCH 409/854] add image for stonesense --- docs/images/stonesense-roadtruss.jpg | Bin 0 -> 614174 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/images/stonesense-roadtruss.jpg diff --git a/docs/images/stonesense-roadtruss.jpg b/docs/images/stonesense-roadtruss.jpg new file mode 100644 index 0000000000000000000000000000000000000000..19e71dd704f0a4edb0dbe6bdaa41d6c5ed282fec GIT binary patch literal 614174 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 z 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 z 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

4uCl70nz7YfsK(p(u%umU`hMx2${ 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

# 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 z zxueH?8VN^-e4e1K&{BBw$)_8u9viTB|p+$gR>IqA2?fKW%?yG^=_au zZ18uV;>1F9M>5Fy^>Q&9F=43$;p%jd3q~u?1`OF5f4& z!dv=^>qyt%yVe^1IFX@`b?h4wt1a4zye2VV>NvC_6UZ-EI^9DxBGri;-7M(0`J38N z*98p;$0M+sVf&gn>l^Zip9!D4ACjI0)G70-S|;l95tdH3)tl1Jo$cfVxVr;kX>5p% zWKq3>l8}Cv9)Af*6R#q5v+9Ei_I<-+N4hbb*Iy=UB}V%5&FJ1&{{GqJ*$p-AazIIF zKYwCwY*gsm>F**HVxQo>ALGbZqr-~?XRe1uHv%}uaCX9V@66LQRUX9_d1T*7hYQ+( zWvv7jg^r23Ufwu%Yqn%&q`IGb*5-bY*U*vIGRqfIc$gSx^k6l{{qU+jsKgg=3`sT? zQRsd=8Nc*W%-ip`5%f)LYOh8<_X=ifY0?SQ8C=K*hlz@+gq6RZTQQF3StZQQ_!#Te zF?SuaQ|^yU{lPc(gn}XHRA4bs>HN+6P}(+kh5RJ;Z7g!*Gxoe9p!Bf+`?@6uwL{mg zC$_g9>SE)%y2!Ho@Knq{xB1)3qHYb{!?5k+NmKL)cK7vKZ#Qvxta?F8zjs5k;M1_m zo&CX9>WxfhHT7t%8SithIw53Ynmwqg*B-8ZR~ zNNZNf#>``kDVi4cOjxaFO0b{o_~(oeB?KLEQ^U>D68HwPZhwj5rW+LWi0&A2%uJOT z-KKc6G2``=*H)^YI@ho4kYE@oy?va(*UL!znaTHcplWTJe1|3y(?k8-mu@p3=q z(1CQb@K^G-qN5T_pN+D!WnX7n;;Hj0V-UvDJ2^wVX2nA@6@Dh>$9nQ?k_UT0V-GhlK z6T+?60}w{)5iu%Yp|Gu!v7Px!U5^eI;nU8EDVb-a97~saRZI z6<>~>d40*Rt$iyyx59nwc_>e&6dzxg@APQ6@UUg}o?%sH?W;SKA-o<2cxCo$YAI@w zOuVlN^O7>dzps`#$DDcSg45hAdnP7`U8@r1u2A=8adsD{8=eZw-Ocdm53wXr?hEQ$ z20QPm{Tv zOnfdOW%m(cvJ5U{)jX-dY+$r+#J_mj#1VR^ZA%=YQ343=jiX7ey6i%|$841)a`ZJ} z{iMnkTBoviN~^!D@%n(kqA+=@1a|nrJzcMd5!bWHe7wCYc^_f`L78?$ILJ~Sq1k*U z(OD5b>%!XLd%SJ^WW^8{eXA`*B)mRyn{vk)7N}d8EI*}BWIoN#J;bPw20#bb&nxct zs?W)M(~!S^=2Ia#^wt^R_KoXNGH*vp66xjM1@$WZe+_J7{v6m+U--Xajae)UzivG> z`q}RA8+I{9C4 z0b&vB8VkFwmV*gC1XnV?ybmAg*A=;475ms_UpkuV{ffln`@U_^gbB?|hm zWXS%CkJpr1C$dQ-WVykJ6PVsJBcBqD=n3WNXN=7teD>nrlAqSmmB9ZBOtUO@GEF$x z*GdkPpCi?rXFqiR2e-7_SiMs^r8YwP>sq{_*9V^3sTuYoPGe)Wo#B{G+ztWaSP@|9o$#PUw+|9%ty0w3cX%HImKODGeH z%}?7R#=j!Qih46=2Hdw}ig#D3RL_mWF4VZ6qM`+EhR4ASx^sDmC{?^~&YrgJ z6@SsUB%bmij(YtbZ|&ob+SK6)rZCp65bc#fE28iBHQewRb8cU-vA_OouhBf4RqGb3 zCXpxh@O1123*8o|2$(=shm=!e6Vz=943Ga!U*7(kzQhiNLWhhJ2+yIr8i{+keL;2m z*!j~@aS89t+McF4D=53Zsz#i-)2Cs$`$qBYW_L^Y2NHMv;W=}B!uM~vGb*?bN48Qt zU=qg|CPtPh&?LY4QOf99X=gT}*fvcbmxv3bCzz|oyOZ~^j8>cXL7!fbXzd-Ifs|AE zX6txfix~B`6ElEmVR)??IRI3>+7szuxz*Q;Zz#>y;jcMgkzXr>O}qK1N36K5Uyj1Q zuk2_-Or^s3sR0vv$i{j|aC)<_aeKY5awwYz-U>4%fp#A4$#0kE5q`wKq^W)b*f@C_FSiQ|u1v4!uQI**fgGy8(7=c{8Yz5nJ+r_2 z9RBSQ_2LQ?BtDC7KmuxQLjkf<)}Ox#`nP9KX%Bd{&h?9Uo<#5|2dt5bsXM@o{%;rqfULKme_YNW5DQ-4Gi4rofVZag*(i| zHA6OY)>d#@~b{b*cf!FM2_+bdrtNJ zE19ceL*Meh<+0^%>&O!(>$JwGzDJ={^D=9ipjK5GvkKV5R}sWfV^K-(gera9 zvBkDOoEoNAGX!{+`A9g)AD69eZ-RtJ=64~zG@(>B)$56rz}t=_6KA0Dn-hE@3Kj+{ zeZ54_q}dL7G#AqTCNRDJLtqy1{<~06xA|?n74NHJ0(!Quq6L&ES3z3C0XMR6yvuw?;Tzd`)yx zEW?;;hP*SYd*2cU&o214WSl))6&6dg#bHv;P2VFUm<5>2k9~Y3P?nw&CHE7cyzjc= zrqZM=y!BcS6)}cl*-Fz+xcytwW0^8H4odLBQt83${QW=hx_Nfp(Q~i6ffg5mYesdL z_@`5w6LhS8hM$G(oxG-nwIJOBa2AEyCr?1zo_^#3M>YY4_j|jb%S?#j96pYn$QLLp z@QDC-(Yfrv*pDwpkV6$t+Nz<@TbnQb=T4I9DlsiztNz z*s35!EQVY`Q7bc_K+ZDeUf0r<$MAXB@=gWnp=vMxaFTvne(tTs{p0h1qkh~lB4E>} zC_`eYZ&u?g#Uh9rG@?KzO7*y<<|}SCFbfKpk&}Ekh$?Zul7b|o`Mtk=#^u%>n#Aq| zNAMaaY1OG^$8=O89Zw_*l0gkfA62h8AO~jgVk`d;^$pTHELkjZB2@w4%W+sRH5;vO zi7;V^o*8s%K$g#KxkUKHEj=7C)2lw;Dm#q%=4b5Q!+GrD$9B;8J?H+3>C$0k=TI-0 z7rBT_Li`@V;ab@L9>HCnjH5-?5Kzc~^7e~|eH=kD*7kq8*FU$=9@32y*M9Zp79OrigzCBGd&)k>d%jB8Xy!W;3?5(o< z7hcOvdY`}J0fEDktXXaYfV_^uCkea_ogW*TysQpvuNf2{0AJms;~oh_1yRp*hdlro z@R-l0B2=KDE{1Sd_1`b}1*dL9{AqH=)qMVr6%<0boqhOZ`7&Ln8SeFzPI1*ZbqxIc zXH{|yddN0s@aLj5@z^50&ZIxe-@hJ`d}Gwg*GC8 zenHuOYnp5R&xCQqWhH_G9!3r};6WoFF$+=lG5M-5=GD8QBt_$Y{H0Z1U^rk0m=rNo zPO1J@SOfo2SeLtPjQ+g>d}Gzld(`>6adwov7S#QauQ|L!$)8RSa+AK&)7zn3=lw*M?`*)$`|y?uiqg#Inhc|>RE;YRG= z_ZM7=j)nXA)cC)PU?1IBC+iew!86lbN7N4QTIl+`D9qr95cBlNzLodj77+nsoW2P; z;dN+zQA6ZzUnIy}U7BXM63`iNBf~nwX=gPQ?ABM?*YT1f9fJdq6Z@=rLTa3OZSCR* z-dBaQ(g`*C8bB;y_j?VbINW5`N}&=W+)7X8AYH70_7>h`!xB&0d(X3k=rf{iC882C z=iKma7zQ>PRxvisDb2!m`9DRp+(F)3C9R*@D9AlA>RX1#JUowNEVtm+PI?0!7SG~j zi^MxpdwImI5M|pkJ4tZWZ2p5Jy$B8%Q>|qR;av2gSiDX@l+?U5tLX}3L0LkLz6|#A zXB~dy#(T)ec1~yFCia=~O5}(YA8--nV;$x~8TT=1bj0oC&C0_#O#H|3r-_3-nVN{C z`XKSN5sO|Q<2YqDwOGqRO-bF(Y$sihM|=dYsB(^OD$@>wCoWOrXuTb;YQCL^wzPU~ zLq7XzteV=J0+(jY`+B};>D|ItQ;>7xc1Zx%iltYM@H1z7WxXrojL|sr^3hC4$GoNy zpKaZV*B#~BWOIDmZv!p4C>TnO+Q(*|#Q--hJ(H`e$y(Nry}7X;VYUYs@Z6;_sTh8f zE*8e06{S(}qyv2@*T~9JHkfeQe%6%Op7|RPL52{&%O+AI!|5RuHB&a(iQ~T`gY9UX z;*BzKdUxLBYX_!xg2=F4A8dJM6Qm<+0~SU_mhB?UXK4K12Rwre9wwk3hKDf{S;Hfo zfga-@V#yh7TQ!UGnG_!gPe8ba2?al`WR2`}HgnFm(UZ@*?5agOPSW7Nz4>%fB@@gGuo^3_+ zx@Lw_?%(C529m}^D4qQ@0_kFWT9B)VFl30utzGg0e#MvN<5BtB&p}5; z$gPSniV`Jf^DZ!ddhesE2F)BFTKkuDWr2cFeFbLp%zSqk4We!D_(5N{eS{BFT(3@} z8rbW+1a&g@$}5=R(IA;=GQUzM^d!jn>PY&4CRK@CM3Jv29wz%dusGZHcrvgDYV6GK zLTN$b_C9Xo$z?HfiBA8!W7<=>dx^%`wnYh>-dJlWSi$?EYWrfR;VI%haeS=t^C5M% zRlhJg$pDl3;gGllAqpn>eu>#bE#tfXUgerQmT`T{2+eGEQHwUOgMHVm>j`n#&#u}p z-f7*0BCLdU+;^|GbQPG&YPq!0X(Eaqbh!W1Mr^38<{UdegeuSQrG-7fDde{_8lh^{ z2be?@jymX)C}~2jAUgV-RXmT-A{f^1f$)FP%t;4@dv4_8QRMXDv7dD9P^Nd=>TBmpoL zzCs5-e>DflIk7rY0CM_CTs~Zy0R&NdMFYXOhPwxc!&1Q{A`J&_qd7S_moqb`9S;Oc zwop;HXwycsCvA3&2h_#}xUXFCZW+wZR;&*`ZueO^L1y|iiWOTI%efGH|9EqgTiz91 z?BQ-Jv`G#s9H(mJNIny^x0@UU8a;&<9c4KKd0|Y^_u@S>+n(JHxZar-Mn2TOG}`MG z!Ci|9v9bmG2F9aPgYFpPA16jXW{x|1Rw-9xAE)`>r=S6$p;HH+kAFhJ7lV_4G@dGY z=(9YkFz~S*&AFMANf)zAEey0OG`o#L`F5;v-Oo)SS8g?qB_)@R%K3i7w7)tKe=nDx zQW*coVg;^aj7QAtxj;R-jSFJlAk$WJgISrYj&=2Gbkf&+|+HnCp{eq)o( z9ZEEUn4K(#9NGBZrBk%crdrdbCUERmC(Y$MIXkNpdLloGd0CBwzQZittY&T~YSbv8 z`BGZEsqopBem)}S#^nAP>Ed|{qr|Az*X3?Xbi|YboZ@(CXL#&rBm>m!^(gs4+iTJc z#&T~wo6BI^(X$R7n3`C;Z6zGOybKe?ar=!M673t;k(;U2yX}(Xha@T~Af%l9jBZxU zWJNpO&nx#)aV!}36)e2GifJ-Ymu#~_72feQ;}xhtW_ajV4E4@o7Xv+ zSD_Q%i=JTn#{*E{?nA|A9A2oHPA6WBJyU}k2Qj12z93!jOo5M^oB(8rF&?WpKp9c9 z7J=UKU1^v5wU_G~YCHrVcXe`GA`i+gqDDOj6S$#!{=frwDqNbk9bP3%F7v7R-3%b< zVbheVK%C*^*>tL}x?=vo3*Yk+@yv(<>5~o++4Mb-aSWH-R`LOMD7AZ>y<#Zzx$Elk z2j1(2+zr1Nd885~{#IR*F8awQnU;rx%iDWqfrsSc3xIe+>xfDvDrQuV2@2U6;D|m_ zMh;75=b4`rB`bD8Podh*zSA2Z)ed3+W{#Yp4qH4>?ZCJ*Hb~wUuK%FQRIF4Di77iLq7{6R4)9i|>C@6l-9PZSf|R-vd=jIuXm~uNf^hC#V@u!)s#h9*Lr-jfED6+r)C>tNqf?d5rL|LG5%Vu4y`1S?QezxW22*8DT0u z+aC)G`z3JXA!vkZcu00Hf)jIzG4=fO?t*3RHzre5{;0 zzOVJKQ0U#Q*M_$+do9$_%lB(Ex&7)tx!Xo&1}qq-{f0J^}SeIJ7w@LABPNOo`Q#>)VK1b(Uq z^*3@JN!)YUf(N)U<-c@JS^*J6Q?wycB#hJe5SjbHmA2<6=oeuTRgPkUI+YSy2Trgb zyk8(1#P6vLM!&q2aM4Oc$I!4VhDQdB2p}PY!nA}JHMhbE=)9`WTZCwn0FC!orX#QW zJSE5ukc|YWhR>AN!jqwHA#6HHobWAXg`wR@>>#tyv2yyrUWV|BIO z+4bzyf-kK<2avm&poap59^UxkgWg=|H@FK4vF~_-+0m=0r{1A`lMUOeRT?X3MLO{= z4mLp@z4xhjpWgZ3aEEoOR+~ zOdfcGMMqnhGaGPt9m*wNo80_$9~DPElCjf!gEd=VE<(PfMt2t()QD1Y+VmFoM)ae( zeBWuZ2h1)HDeZl!>8eXNp?2LLAoboCYb+%HZ-7_>4qEvH%6n4t%G&>`o_@vDry^+R ze0BYpv^|Op((>TsXw=jjz0^{x>eXRn|CXX;*VsG!1%`fL=3xddA!}DS&j>E%dWbL- z;ve&%Z2j)y5hPX#NjvHv?5RX!+ z7b6GxBe+CixvHZdc_*#Ml&{y~4vffZpd*=cH=xyukN7pc%hNH~PPU34<#HZb-s&nD zl_XgikK}`0$wx}8^>s**hUtA$Vf<-JogfY`rw-03p9U!AiO{KA5H6fhlk_s`v5}MR z+1@~)$e2v;>uxBOYO>W;o82qG3p;;3fEjEylN$mQVFd?^=#=7%$k$7?`EN<#s|*0A zQNgzb22*5xuVCU`9877 zN9;L4$@YQT1Cya8OXA1AHH3+Eu7nLE{#z6il4R$w6W|8}A)27$Ui~5;X1Y*0myym& zNPkZ>e0=#1huuCxb!$HGq}cJsE6aGL6c#BSyY*m)0Igx<$xlC(h{bs^i$|j8#_Wpk z`_7DZP+Mr+ri)ACA9&g+bpuUV*Qld{jfGqhv9`GPJ5+b_4U5fK-&9^qC_QW6UScEZ zdWA2C7>OU74RxpWKUI#^NR&3zyvAi;+>ZQWPW2c= z67zWM3!=Gq-RCfSrSTGDOppZ@65bruei1_zg419?wXgH4i2fthWp=gsN?I2g^KL+_ z`!^Z-+YNg#OiyR0=&i|p>y$#P5X9tN$5JWVhjF)JsQ1kBP9=P%j<`=m^rbV`qVWK~ z&ZczMX+mZ!*Ae5()H%62a))c#XH&c)a?JV7cg;3zvtALOfIh8tv#^~6E6DXWFG*6^ zR`zwD%@at%pOjAE;{I!H+ z;807aR1aVwCQ?`@=accrHdF!(r?5s{(+t-oX4PFyXY$y|oeTMJ?3A0mLZrCV^$>lJ zs_xNeU_(@Q;NUhVCny)+EO^LN-|BlNU2~uuI|ER>`}Pn$*Q??Va1sW-RmPe@zF6Wx z%GmR3?AIB0w{NQi*n&v`9oO}?dOv{UJfIF4^+*!Tz^f!t1P4(ozl5faEflNgz8~9^qh-o^?m=hf~XRvk21F@ zx{HP^hqH;|@A8F5Dw-isyQWRE_G9A3^|%aNYFf2(m8$vx8ADLp-L=cJos;u7eBP_z zD@rWFrP=~}jOmQ_toiQL4u8zv{mcNTZGk7iFp_fmXV$?_R`0h3jc4?e8M|>zACxF3 zziSu!i4&_d&+cF(t@`@jOA%2CniU0lj5~>QJS>bycHbb*`cbo*1c+SOgS!M9z}{6I z-LG&$1HB2AdnE9(*iPy`%N(~{Y?+Nql04q%4rQ^Xn2UR95|eV~Fw(btUYjjRwR*!$N<6Ie+TNs5sXUMUk}`G+U~9t5NPBSW{TeR8>15Uv5cPm0DxRAaEG;IB z<6R)>t_+F_%{@>>vPVVX54k72JH!_1fA*CZPQ9e^;z6{8GCrBJqI1rBO=z@VdFt?k z%UCNC3S^E=l6Sh-0tN)2HcHX&;LSZPeQ4f8#ak{~b|U~iqJyA-|7%t{m{L2wG=&@) z>5ie^-B*Fhm=g|p;c2fp153Zvp& z9Emw|u>M@=^tZ>%BYAAa3iz#MP8g`qUKuxwy9*UI%I;&J{>U-7XyAOKdM(QJ!h5H0 z5Xg4VyQ9k(TZ;be+vV>yMOcx4ILqJv1>WK>uH{;zJbA=OImGiw54vD>IswbL#WFZR z&ZQjh*rB8&ad}p*^TG5-4xDB}^YxGC%%>88??*vf^Qpuz~><$N=Ikj-z0=bTiJSx0(eD9^!U+ zSw0p0Bo5lN;`q2o7n5ZIgo5im3Bbpv*M@c>Jhh6Z-KEUqh_$J-r*$_VnKmRJWT6OT z7CU+~%3{_(Qzyu5N+*lwD}uK0ou3g-(vc-gwMvnTQ^vb=AbQdbNZX6F; zKK`iJ;#Db$)Uh=9VX^P}H0zbZv7~!J_h##Fu(r+prK8gu%-(VNqcmgnlvdZr8#HDS zLG@edZlL{;B#!Kat@HKh!hJv6%3D-2A`t}#m-PwoUpnwbVZT$av6C&JW!=1y2ky%( z#y!>1oUMNtush`h(17#Pg7UzcwnT#oZm|b9Q|gmZq33;nj=6^^h>V{Jo&0Ca&G(I@ zS9o;)b5PdawnIkbWt!3xYx|0!TlTn;?2nkl0Def^*WqFtR8uC28fQS$FHcdEu!_U& zwEW0;)?8A3)A);pA(yvHCc>mL2%)_9(qbae@!}60t zM+|W*n@uEd=o)$K&A10s=h-@?gJ(uwHXMNEO;#h$1v*P1 zLrj1YR(jvk6UjyYfH9Twn4=ghxO&#IQ{f9NxRE#$M(=sw>%kN}KmoX42{=_T2Uq6% z%>|Ma05i-l=!_N|)A>ZI`M1pDbX?j`=*qY94u=hA&!H}MaFA7E0eg>*B3lRI z7kTE5ejU&8RiLvS+6~H(8v9XP_ouBh*ma9@;Sap~Y)c`D-$@wAYqlwjy)bf1bGL$N zrIV$UQ+@Q>aH`C6uPG(Dcr}2*s^yLf6<^=oe%Q~zKkzE=q(T41#ne?j&@9vZr}FUG z(O+Z2!vTILs#tnr{+7re`4G>{h)$Ci|0qiTq5p7-QVpD<^iNOd{%=p{6%O3L5B_t9 z+%Yb`>dpkNY>-=hFatoJ&EgtnJl*Tq^(bc#4x945F-Gh;Bsl9IPS{t^{LU)VJgb&eUD7+m<~OGuu}gE^ z3qPz76m$G5NdrkWtQOZ{wlB1v4*BQrFHKt|lQ`C>F-`N6Rx#k*onSNH2CCyxB%Jv3 z;&8I%&PudAuG57smZRnxH@RxAK4JOG-Kk^pG$ptt72M4FOh;ep2 z9Vb$5nv}PODX7YE6a1c%)%@$VJpc5B-i-1i)>rnakrLrk;^`K0UP6UuN~5dQtlP=R z-G_;%(NS*3K+c!Crl6}mjrIGDO)7l+>*OkE@{+*b{5W_z<$3i|I4LVxUCZa|Ad?%q zcFw$MwjazB$nsb3yke?^h3B{o7rt*-hj)sZyKLc!p$P(+cFkXl6ZrB}?N}Li{KZvID{YYyp?nFsy9^oO;0a zy!~bZD+E1Wfu9T+#~W}>U-v^%kgzvVqFv2Yw^b93$}o`~6NVUG zU%864{^hMu;b*3D%t?beZgjHs>?~C6HSk8nb^fAQluws?9?+}rG3ZWH#nHT+oORFB zxz7U+sN^_)Dm!=S+QP@qw;0F7>BfZ&ESrZnDfMUWJnoGuUrcYii5lE;mNtih&a2Jw z(mA!v zOV0Y-Y~`M;cVu-ppZZrJAwjg95b}Xjk_{2?{LUNi9lTCLpI!Tlcr^)sN-L4tUNP_S zkWbV}b$<$INkkfH;@!F$VL+2N2}0&|s`CI&vV|DHzQg)O=3GvAT{eX)v-&2FP^fJ>Ta*42-H8PPx*gZ}{O$ z8{}dk?wjg08)2_pfEB9UqyhabO*QA^4WLh*b z)yphB0F5T_-(kzQjP8GIv!CLhZFVtkF7`hyj}FSWoF@Oi=AN(z!Vg9s{_k~pD6u`i z)RV&UBkI7s&W4?t(<}tU5tA+;_2(oP{^ulj_V*;WSjti&jHz!eF-^$*uNiR;7(Ll2 zQMrrm*&La(jIDXkkY{{Ay|n9zIhuT1`KaJGfQj?%AFAmLH}r`S|M1l~akCQrVcB(a zcC*4I{S;%oYbJHrUg&MxmCdpX>VZva70QUmF1L;b7PgoRBnir@@ISPyo_DFG^wcki zx4lR5^;e}OPBXLaX%_NJ3jgXAWGX3*%Dgv6cz*vU*38R@(tX=?F_~=%Iv#(au8Dh6 zRACL2`T1BlBNH@7ncL6It|CS4}n+` zl%4_)E{}lf$;$0_M8uWYKg^4V{(;vo$nJO|W{i#U(m#0|zCxeIfY7eKdp;@|pmBc) zc*bXuEYdBZV@}l0U%Ip>uhrLH2bCWv1Q>4fi5WjeR`q@FTiHl_m1?N3$8JX5$^|4H z065}Wve;wl@A_7L7~bg4#aa)UM-tuvYqiiC;O3gvCKS;YOTlcb2rA>8`$$}VL{i1w z%004GAVTFW!P5#k?w1joWXXy~1l2ehNXLEE-><{d_ZO(~j<=lDnTEbwQZ zg{p{%1J4=REQ}qB|GTr;5|a@xh!=?-vDTA4veDX z`K;rU?02YwV(wHoUYFIwJ2RSmpzTSSNe7fZ9kJB^k)BSt7Y@1JuC@g$GYVxC{S=?;od-!UfWB^x;nZ5YI}(4KvyG ziw$e2W=kXi=D6JY9EM^#?ggsDjMHGL?aUf4*r#uG{ydvvp)9bkRV#Lb*%$Y7>LJWZ zd?x|Tu40Y{%mD9d<&L<*sTG|I$&T!?040yQqaQO~2JS!LAyq_=sPiSHMXpO(8n8)^O!CY-gTR}+$w1*hP|=pIK!X! zg>kN&Brt|_-$lecu^d&wOE6>Wdf)f=dq21CiujOeQ~19 z)ZdU-pYrYp4{Tv$-G=r#mynqTRZj#I5VliVrXYiBFG_xulMz=8$1s zJoLC-rlkNxFlTH6Qi;4iXWpi_=ZK$Q(A4R_;frJ(dh?#HDwTH2Sa=10z25KnZgstTVD|Xrzm% z9#>W1whBDJ6|;&1^|#^(zvz$swFc`zXXL&1`0Eb@NAA|$$M850=AC$NNe2?K+P!ne zvZM0}Qtu*(0`n;4gsC?lAd-T1+A+GXdv)T#q5-BDlpd|O621diKlWMo_{o{4 z#7h8@;{d5_y`*|@j}UC>WM0)GS#ZDKND+L$FDSolNJZG!m_}ui{}uT0OIEpK!)q=0 z`F%;;rS$|+r-5@(0`5MqYWHTWn!Ob*yY9-$jqMzsp44!nH>$S4*v`5i;DMklS)G#r zo-uoC@UH9fr@6Kpj(RlQ$)ySTS|8)f`h(*9G5-`Xkul&}D!z6aUfMg9o<_J-1y}&SoELb&gfK z!N4s1J0sjLw#TMs2+D^L^sj`CooBJD;Ln>Ae7C1w(KXv)fJXK>6HV;`rV=$PU+As~Fp~+U-2Uh{>Hm4LVHj-6XZsdvz$i(>9*7@uq3$0)+?UG6 zOU#f-C9`Ox30Wya1lNIbj-7T03Vq1MKgF#RSNRSHsl`Q~^9#(2NT0+}@`JUF{lf~@bm0_OWVOEn3R5JLXx2SNSr2LUb& zYB}D)G<>-uKO>J1)V6;LH+`;jqD_~+e^aMWFJPvvwyvw!Nn38FRDxSmD+F+0CFSUZ ze_Td83OL|^Ruo_7H%kw8doui_qAM0LdFHTkn^`57fh-dC!lb@>DaI8`btr|O>x93< z@&Tu#Z5r<`DN5AR@+t*kJ6Xz2U+*@$gXjvhx(&jGrxcp8v&1B2H`4yCe$!BB*Q>;y zoybTj{;~1*zA_rYSaofG<`H8#@o|T!8yzg+!SJUyB=TOsHrK6#sZ8mX1bVI@Am!P% z&#K>*4jPrgPbv0)$dkZUaFQ*J*^M;)uPfwQBe7kphx)q%64IU_Dv7L|;-8B}SAZYJ zxdxKzOZnVR!a6h(mx>m%3aEI>6?{%)X^LX)?TH6>?uc|9j=G5jd*P0(U%$C)$D0f< z?K;HOV4~Pp&m1Nx28d)r6uQMNpa2HG@HYnDAk9CVWFFna&cdy1^dz^bmjPSc;B)bC zz>@(;o;B4PIT04;+EF{B@4W6=-xk$AF=}dUO#Ha4@ zg@iHTfo5y^__n$mnJQJi+T4P6p{_+zk4-WHX6i(<0sbJ%V5r8$9LXHulbE`skG$jN z&#E<}InPk+dinNr-3GF~(Q%z08*$wN&uHi1aysQxdPGv}du`wqa`WwA@bVNX=s$0u3ZU zP&6#Qy=G;qHW#>lG+ygUGZYDVC(1+nLrgL-ez|bdBMUa_-+UaQo{+dR@Qwi@Eu`|T zTm7MqW-)s_m&7MmnZ~P`CtJt^u8lkcT6KBRmVM)H?yxa8B4bZc(POXUi|T6(PP_b; zLz7Oyb*rV_kjawR)%+fI0{XypX~*)7Bb`n2qA%`*dh6JOF_6UV4;iKXA};YWbKQ)# z3bn_#?WEnKNod6BuHR6gaNm{0O~`Dv5J=#Am-nE^@rs}P^ba`w-v6jQ6&%Il%R1pS z(OPAyP+mdy!elYss{4KM(0jRzGuq2Tp zD&BX^3kw}a(nvdAT3pld8{R`#9E{6?J! zF`%tsA`iu4=Xgq8H857l@zC$-w`5&)OxUMa-5scNDL1Cn^q5?uG@rdX7O?G1(j3^p zw%2tlAa0l&4`X~vZ;s{|qBj<3Y!Niyj6Xm6=B>=+Hotr!F-j0ccKs;a_;?IZV4=X_)2Hqfz18Fx;ZL_5sL|P@SPbHj4=ol-Wu#FA*+jI+j95 zr-6(KW}Aww(tB&F*H?17sxHC(D7jvt)pRZFLq+Eryos4A54H@IR>N|QMDYR1*XMqe z7eXeLFY1I}z=F$IjP2Z6%|;9|{B?B^PE^zwKFQkJ7PDV?AC5{LoM}J1BG#B0`pxUV z2fW$!mkG$kuJntt##w#fsP2&BS0~(+^jFoiN_2Fn)XB~$>N^?7yx(BX84<9K(-mb+ ze5}3up5lPsZ|5^DdyJZuXX*6hxp>eOK9MqbK7ea{Cy0w%#JU4KFk!U zjJ$L?ed)EFsf8p|%zm<2#&hUr;t}Yk`8^pGii#6wd6R_IyY&2W4{D0ygkgtORwxT7 zw_BPH1O1=9p*5Zxm*c+r;2yWbvyI(A_$!e`keZ}we|xRQ1IdeL&vK8h4C(XB^HK?V zy{DdF91vN;)pb|3yy!XgFm@%PR;$0X`I_P9Ezv5`-!$>#^3FRu3FV7^!$%DRpDkW% z1EHY)pU)iF3@nVbA0y)}MsmW#HB+z&`A7n)xz-%EMxc9O2kavZ(q`(H>U!E})_Wn5 zi3{<2M_J=8AF=NN!=he3$tu1tiC-25ou#{A!%yJ*T$YZiXZKsrnb{ARH+n z5|(>WHemY%=+0$frDR1TJDdYfwffS?u^eD_gz}xe6UhM`wM?5SW*h;gB z{u@Flj`NE?SJR^A*`F=i0>La1PePPdpfaTroT8ULP3FhZoI)tFg=uqR5sSc=XZB*g z4V~hf1{QjtAUnhJ_pwLC?#(NissepjmptNF)fZFKK2;4_bt%$}acYA0c?)*F0sGkcvAMHE4KzzS|tYdCK!KY`}E^qVfs|zeJ>BY<}Wm{S&rZZYvA9bWPj;hbs6Yy zMhi0%JxO1PXNu8bBciMJq~2Rbh<0e3A;ICn@reL%oku<}C7b#e-kw4fE0^s_cP?m( zU(}j7NrAM|EDN3-c9yNP_8keWH%`Y6D{%4(1>$^u2a0Knf2KIhHM@p&10xBMzy`wr z8%!VAU5*WZAO4 ziJ#@tk^5epCjv_oEL^iQ#3KtX4-8AG179-JzFv#85l*>~FnQ9MOwTPU@GR1Rkf(YV zfYt5|Jg&NxM!txRrm(qUn)FgRs(A(MpdL&?>+ZF#vP6MPTT>|Dyw0{ z;w(C;`ju8Ay>zH;PkUJl%kb-!MdSSdua?sZ^`W4?>l`Y+0{IYO&FY=(Zl-qW>sBIP zZTohV#$$rK@^fV$)n307q2C4V{4s7de|p2E;qj)zljtNr)JG`gdGq0EW0f0kgxCua z-V^FHS2ZREx@vr)Wh;xM1DmcmZ=Yo?ufQG1>l^;|^Al$B5y^Hb`|V#hb6| z`22V01I}CGOTUCcKYbuf1bs@&4PEy3osg8)JhTiJru&XwyNlR}iee5RdDx^KAQ&vX z*Q!M7>>Ah*(BT&DrhKfgE-VVEFjFMt z>3f;7Ib7}5{TUHo6vIenauXaClAzpJd$!Y`+YEQey4w&;`6z^a_%`bux3!}!oKE}; z%)@%{?6^X1Fz9n=WupAEk(R^Ub@UV1$EpxVA#Q41;)+VWcS@7wliyF`nl2E;U@3Aa zE@czELm%h~nBQP|Z^(L(K|iknJmi$~iaP`hz0LNe@uZJcnN)~rT&zHG&jN>8NN>;v zG=t6!c8b5d&MqIhszKU)mLl(U`Ridd)&{Zy8TuX|Z@jat{PywkDaXbhS_ZNJr~uH2 zkj4xVQ?v0)p+7Qpe#Or)Zb!BB4*nGWLM2&!eWAdK=#h{#Ctzzmt9gIrr^lV`5qXF? zf1;GOuIq~uqZ6f3%WyWRzN<|U+2UQJ02vYl#Q^l&Uf^@~5m*UoL z{=I2sd{>s4Bx|CSE>i#nVv|b6%h`pX1otR?5)S=BPa?6kKdAqyn>LiSUon_hw)SNA zqFUCld={CQ;u7A#`QD5WgK*mGF8!KsT>7;8+fBLt3xKgAx@2z2rl&CBTYx+Ju>nW9 zuiMeZO?zM4ZOGrZ44?Ch#Jo1k&*SR~AFafuCHc5@{4w>9KY6boGT9+20W`H({xh*cDAd=nxB;S zicur7m%FN*ynWoepri`>*M1kT%CU0ei>{ddRA{#%1V_qrvt^?dQx~g-xFaXL{3=>~ z^lUtAwV+xrjZ?G;8v%HYCv^W~HD)mw$cq&Z;yM^>(7k(TZYPEA2%L+dlymNpy`P{D zHTop$N@ji%+-93o_U?B4g-WP%OE?y%r_3D9qaQRN-YUe@V-=Gv6n)x6cD$BHUSQF z_{cj!=X`1S2LMCnFF2JCe(GZ}26DS}DZ2hY8PM^Ri!vGu_<;TMb!V*YU(#~}AU)sz zEj^D4=2#v*rYi$tY~-g1Ayl$rEPs=4!k#wA8kwZGKp z{qgwx)7JHoO<$EUs*LufB#`GG!r%6A#M(IEwnJM`t!dsE_sAuId*YLC6oHxx7ow+` zOBa^gX(c`^dEAr!=cLsGVGpnDdPtJ~V$*rsvL1+;;&v(2OxY|*&4Vy=%wr#Pn8S7z=R+h+o~v`0geSzp?8K`foZ zb2wU4?yb8g4yQ-snjJe|9Bh`LyQ*s~Q6GrJRGb@PDkpdBMr$v_(wS=qQaX5565|z$ z%On6NX)f{LKr{?E-De?%fiuobQy4A8H8uwWo32dc+Rgd-JHc6_=}GNPasB=VpDg*` zvEYwAH5IMxG`u^L6vq7$Ya9}xQ%>T0Xm@fl$R_DY5|EB4VM%D^;kXqr%{Y~0&3&?} zeJ_gL+}n(gfnJE@wFCRV-Gu>m?rK;o8$>!0f7j&{Ig?fy%6#W&tRw~nGM!)KjQkzo ze&b#1%;T6_y>X%+t9mGV;hjMFH}sfgpiN`{*dlc)?1!sw5|V6%yf%kIUfBPYF2c8N zX0biN;`3>Gwqi0mxIKVR6PZI==OPDa@~p~n=5}A{xuhJ0Ck33C6MK>(Pgvsw(bhs; z6Mc`T%#(G$&m;+DM8hpp1ya)6gQP-l_{|=T(Hv1fvWRm?{K1^Or}mVy-~4irR&R9& zZE$vbMc>-hYSt3Yp`^@OZI-pzxpg$ISM1Zeg}*7fTnDF4v<%I1VX8gcVh?SdEs3>W z+>D-!i-xtLtpHeB{gUly7XtWKcPUDS8x!|A(;HSqEkzY$sLB6Aq`ImdjeB-d`vk0b zmF@Q4W#$rH<7wh{I-F3gbVCu<&so4h+j=bK+BZZ4VkyJzRi&TZ#` zYU6B;=pI&AoPmBg|QR(~txoCiHnbw0pVw}a_VjLjTp8QthApch5 z25Fu*HP#IEIYaa-V z;*&*Y1cUUuAu}H+od9U$I#3OWGfvEvkai+Az$ddI{HBv8s5jI!`rZ|DZZ>ktn&$a> z#;OGF@Vd&3^-v<5AXx5H@(aw9HW@~o=)?$pS(T#U2sWeWX>*DS>y`)-KU8Ugm2#(- zKUh(U=JmqwXc{Dd!4}}xC?Si8aMI)lH?OB>zk7-~I(E02XeVsE67y{-hH9n`Eo*f+ zzisy)_bC70?onXyEB~LtTMKrSK)N>-Uq*L;T#CtLEjKb$6gT>J)t<5@Xz84oo>B$2 zB36AlYo<^hp0KuxQdNxZQfgr{BjWxCHuk~* zTC`)h#GH3+PrmHuM60|{dZDIRGxgBu?bJ>hqD{9=uvK=E%gQIGs5|jO0J#F444Lnu zk}?gxyTz@hg7_2Kv)cc}V)~Ave?-ap!fG*gwW+tO!Im*L=~BbL z@Qh$OPFl!hdMLv;D?)kl30~uf&u^03a*oH@%)Wv9nw@=wiMCuji;jvI?Um3+r3g$d zAcrm(9F>P$*!pufFek;_eF+)E1-IRahj01Yfm{>ySl;A(1J5jX)$GS6HxG>V8(euH zm>YOwg2VqDQ@QVd)|yjvTDoQwQ|}W@Pab1w%RU2HY~74#=Qh%#$K1Q;bkm$ze|-=5 z`pdj{B}bfyO8bCdv>IT1IWoX!~~$QNdc#K_GMcUt`&X{ znZ?GP*r%opcrAJAT6o|8H&++0{@;S6a~yQVz44FRX#e^|Ni^m{EVL&f#lZJp{!g2kmRS8&x-WM{9MoGMw(O|b zx!b1~LH|T7Pm<*QX(@(y5mvR5{XWTgfGPRbUwrYgq#7%OLBziX{Sp|o+COaZ|4wM} z^b(?pxSoKQI60~RX3=EyK`ZU?7sY?~A^pYYy`KEP?X#JXmWCx1b+1e3^9Vl56640d z2443cNH7*bk{idpyMLx7Ynwn}_Zu(%o+X3OB3y$+N;5>QNRD0di@&<~{)fN$=1UcL z^l#96%7uT?ffJD<4{n7vzjl4QQ`Dil?dpRo=BX9JN&h9`DtRBZ%p6x7yFM5xR%dOv#M_B{AvDtXEyA($ykz3P_W9cSb3VXC!RmGEKyQP*+|CcP)O2 zz_)v5{&r&TR&cO4VjG((r88FMDQ|+5aCOd&7iVkf8#}llzwr*vky$VNQi{&fNa&$+7Le3K}qtq7rvTs`+cO@Y}i1T>Lk@?1S}y{uxI4 z&vGx&M8ybnD4r2IlxqJht+o?YhkQF@MO!UX{iN7bdLU8krFKxJBS!*16J$YZpfNsW z{ESmaGY(1W=TL6=twX8ycJ!ref4 zU?Ctksj4e6!$D#?!_k&|;BY2=NAZmi;Sun2jB3lz&kXuv^=qF9xdL?}$oYfqtbDJt zpWbS&Ki8n=Wl1I(@Nht1x4mmMC;#=XXyTC)Zrwc_^p?pS%XWS$id5+)Q6zo1>8F2u znn`=yu%f@=jABWeN&3AAxJ-%0gO;2Hby@WD0pcu&tcG4S0p3M%hlHO*o%WLw2cH@A z&k3sI3sCI&OP5q6^ku&Ii{|*oQ1ui)s^{Xdnl972z1;evS%(mPpTNd5^!M*LQe$EH zpij8E2m0Q77`>;dqTddWEnV7U3K;%c5hd+lJy`j2AJoIWn+7k2R;HX>l3z9&A?Z(7 z+x5?qIQ+dNJ737j1id-63~zKs^Gb>9V>5+2URE3~JaIi-kiB3i(nOoLM@h`QK=hDv z=tm#=RrdO<%T3b z?CrDW3Cd%?m)Z5%KoyjmqFydE$0H{>7)br=*WaMK+xL?w#{uh8;YMm2DHzmwAiCIa zwyGX*cI>v<)6+?K-3ITgufg7>--~o<0jR%=M*3ZXLc0&xq}G5o6KP&ejxqG5tGJ!wx(P$w z$Wb8GFzYQF+d7|{@5azC;{Q45o%bpI2feH3TibhF?+;GX2IEsN1rMt*U!A%W8e z1mS-x*f#|kkB_@TWF`G6ex+1&@hfH1ML=ei&49$X0FwGs5D`lopX(eC(75}!igMz* zm35bk&x_ZCH*eCFzP_lDNzqny7{{$J==#r;_s@Va~Xz4VT3uMq&eJ8n8^#5mQWQ zQ|y?FA>*7_Y%Q_4vkTVhYs4K+za!<7F%uJN+@e)i?H6)K{$$~f%E$(>H*Q1|n6tHG zbGV9juLTk!mGPZCw8jZ@DP)H0i)^p$UjA+!@>=T6H@)CcSpcP)j~)U0YF+YiV`*}- zi0|L`T2nipaGD)Q(PoAND_(%6K|uVp(yMV>BP1fU!-gLgc%XRN3P z_SDIB+s=z?PRRqZ?afF)zAq4zkdUV@Zc<^DT%{Z=!LOx6Y)9Y#a2fYy;muvoO^?r0 zAaa%sCP?`_8pa$?1cG$D`5}68X?(ucZX9Dr{Z?19EP!C$Fp^^_XrmvjR3FZV!JgQu z(~Eu(nCjJ6<1yH=JzrPml-leigR3~yFO*~3Mot1671L;jGV@}KQ_6nz@NqirW5d4C6q~JEdpb>=6sK?g;NTKGfMuvy%`2 zghY7L$YhXy`YUP;9lK=g^?V^J@!G|flLSMZqlww7#*F!tdFFS^n2^R5^*t)fgVU4I zgqPMp+y|*9j{y@wm&avXQ7VlzMX!s?`;imv95|-N?n6s7r#zE|{aZ>7&-~+nQ;uw7 zDcMhz?H2(Og6|3ZY4jI-0~;yY110ZMNuZpXK)&_Y)0(}?*d;+8UgO!@Qm#Y?U|H)| zY4ZURnZ?d79Z!i==J7+@;=IQUZ;lTQI9}Y5pm1(oVjwpQjF-%>*tJ}nQ}X`HExUB8 zv0lOSK+Yq!ocm&c0rhN)A0{vdI!+T8wB%Jh^|RW?%(%ofzJsQL<1hrzP5^Z zVx@e7J6pgn!chD~yJgkYyVENNJY@}ATO!mIHl&P+?=bF0@^Q=l2LYgf!TP7M)6g&P zCfc@q4639Z=nR;!%0%L8JW%fh;%Z)V-oCmsnmo;V#xpe2`>nWn-d5SbCyZxwuT+^bL)4yQ6OEjacZzVO)b zX&q;C>~^qK`7rxXUTELzY_59ud3%yh!X<;VzE$^N>X5bB)VTcTOaA@9&U)MU!1^%b zq7&x@9~^s4LGW7RN7WXxX(QWv>t+`uEk?t6+;iE#@OExDO+TQKM$qM*YVYHueCWGr z#Kj~V;@Lo#KR+H2DUkE~dTzQM4FeBcT_&wlK8H)T1c;}>2& zf#U$58^RIybIl+-eRmO=@=+iPRqh4^C$zPe_DacA&ZjUHtgIGm!|kgE;uzQ(R25)V z^?U3~KDTmvzT}XEpUV=$-zJ8SS*uLs$!l&)d{KOCy!nx!6Ta1*5^xyqR#K2xj~7Eu71}uGlcSr>0pW(CQl*sUgX+UZ zsyfE~3H42Ndx;|1^ceR0H4+j<=fK0wn+pm(Hl^X1`~C&9&@9#?T3(L+9!c=2Bn=Am zIwl{ElkANgWpO6;MX5ZGhNNDZ#n!DG{;Wl( z<_*8qM0R5YUR(PU(D&G>wS^_=hx;w(*L@r;PGfxR ze&MOX>g1OJ8MnME6S75bhKdK)ooI?-6L4C-C0UIRr}s$ zwsf6s8z;8T?eG=8gInr~`ZG)FVphR1Z^+is-WR_RHf3HSsfPiMnfVPXP0-_o<8k42 z=+v42bw*Ct)Dn|UT>Rr=p@&!Ti5qOhUzWT~Ioj(_PL_auXRV7ttub9O$Hfzj0b10t z+x>oU9~a2nK|V`o0b7UBwM3+(Fp@E+B<(vlm+|>r#40?D1E{nD|EAIc$Q6HR7&AIV zHpcu)(n#M)TF18Mj$s|8idT7Jl&C>ZjGAFBu+!6|QG#uuA-mY*Xo?|19^~I@;1~KK zxSobJ+L*oeZ-1r3fBco*b4k<{-_~NMWhA-1w`>RETXWnJ*(F}4`X*rf-5(=^;g83RTsJ$S?x@BwR0rIjt7k( z;6o|(8W8o;D~emCFHcN?mN>?}FT{7_Chny^->_v{y5#f7^m#w~1jMb|Y{K?*DS zG38Kt^`d6Nj6DLLsZi;a8!jv=4Kn#|k)%^7TgtUhdvdl`j|DHqsrD<%P!3Yay#}I; z3&V`Fe!c{}3|5?vXeJNO3<5a6m@(r6TU`<*6UqMYoZ<0G@1~)+8k0SJR~T6)e=@Px zXuJ-mE>+iUKR2ApGh!Oo?>=L?W1P!-{pl!48%V?}Z1$*~?ij~O86b)QTp~+@Zi76_ z7@ml|-e#!B*}A~zK zDWvyflE#;NeMazk9=4MeXr-QNl zwh||O4&O${NqJ3^9dnmlxF@em#TNK1*_A~2ftIW5 z;V(Qc<1d1%@A!Li%ryxuY*JdnPwsy-QGq+2B%+mQ>j#QOnNJ=}`LDH=q4he+eY(LF zHLNoMfj?%Xlt~1AQhy7?@2R?? z!O&P|-c0>Pz{MNwupo-vI$eutQ0!9&Jvjjky><7J(X!*;P;~k{6bO^z2zk@KI6b^-9FiXJeCzAp!Dtj0lAWIKg^urp&)!9it`PibG_rjG z=u*kK4>$H^>2*2pn4s%iY^%+~ixXi5k$Yj!u}@Tcusw20Z;?wtZNA!{Z~VwK^8l7d zw5aCN=e=_5o0DH*gG{#m7m7L*h~yHJlwR$Bho3wm_N8nwHWf&Ovuva*c}iPQw7F*- zfGMqYUWAi6UyE@1uq<!wVtK z^Ni*|0{Jzw@Cy<5RMA66AbpM{h{Ix#+T&h4?-N@Z_W7=Uf5a z0hyZrgYRg#yn)eA<$_CmX-Gyhg0uoB|L`SQ+x)E^tJFcW@6VFo+);>Tf9q7|Gv50b zMwtB%Mi_X(U-RDj+|n&)gjrcAmR^v|s%jW-v zG78qwMnY_xViqm%nfu0echGgKMHV!@w?y}?kfM+!ysOohH6J|x@E31wa`)#&da|~{ z#h~vF=-$kjHi_rfAu_`2I1Lh>k{B{DyTdLHN@_6AXZipPBPfIIyM}&>hnRS@#A*Aq z5B}F5T)j+S}9qy;f@IF`JICQ4U0|Ycl++z?V3#eTNEtIYCU}epGx>w7GBnu zE246cJ!vXkD1U@Xw(jt7bn7p?(&AJcC6k)NCbt1ArdDit*MF zuLb4@u7=I2{$h5O zO9OGvso48UW=S#+pBTozLrc8f!I_Y5EAQHjI0og_qUG)1g@IgR3|`?qlm5NrQ%7Vj z%AQ1DzY!B;0f@~yrxC4hqO3QvPr+)gE`rH2!1{k8h`p{ceP7p?g&CCsEB<5L z16^zM@w;gqPi|e9V_c+iWW)n1>7|{%i-Rw~qR*-#KvjP&dbY>*A@|vO(m+L5vS+d# z@a8J)ZW;1}XS2KBo8(UTdP$4O_U59PLx$TH+XQ3iN6xlS}kd`+?`R)g+zUc;)F$<4RNzlGsmNVAXKBE%gBW6+Ut^kd+2VaFJ&L1tiw>HTKzUJ(Q=e zrffdEW#3vDp1VA|(wR2qsfJ~_2e=FtEBP185Cp?zIK%XR?v~q8N)r(${A~;7d0IB$ zZxo4YK*j0r2G~u$W=L?vgpOgO_;q_+oj!gBNZg2IN}h_Clu646i837J8vvL0Q{^K$ zD?$Va&a}s}xFKfr(dDNPD1%HZj*BFOwggrRlz53Jfu<{Y<%Dd@-;!Zp8&H3>PP#p8 z1*xel6er;TgZsgMe3m!>f(!q#Xke8^Ee=3%aZ$@H%Jb=#!0BbK-WQ1?{5z$ z4!b|Rn!>->%_7gp@mtrrmv#DEMD_X~sNcE$#)a&ZcX@I}LTDP5zks|UWKaGA^5*~T zV$6B*kDqWP9gq}}|CJQm{2%|rbN`FC`}-37dVoj#@e#$%?euJ(gb)fYhsJ+C{KYra(GeESz4{Pik-B!N7$l|&f$dXdy z7X^1X@oijVOrC8I>Po$bNM?irvYkTqY&IiMkIIc!Ng1L4O}S?lTwr~E8$G9bE(v1= z{}(VX3z&na006She*nmTh|hm(*SWv7YkZ{%K8y}afy7VrLf2)M)m#h^nIppqd)To2 z;skiJ7=7sLXHae(k})(+!6^cZx_x-R?f$m|KZ%-f&n*N&!vr~kr=>3hzi0o5#M`^B z39Tbtb7%qf#kMIT_LV^0HCHI@&GXVQEfIRA8~vt@9z}m#x|Nb@9i7}r9Tt06-l1OY+%2zi>;>DTa)0IQ-NFuwr?W&7 zt_<9OLl`J2cY4Pr{Ecn1-Q>LSRf72CBc)sF%&1WImODiuOG!VXX%(kqn4i#_@w8!y zSch!c&h9~0f@0F1<@ChPIH8bVg%AN-vA20Xd5XO#H*@$96PK~Qjh^9M`!T=g!-)u{ z=PgVkBn}RGx};Yp;dScICD1RL3k_y_o}T;d{RnKLYj=A4+)gn9nWs>Mw){U6AafR` z5-@60nb09sN4vZ0(TGj`vQpj7$<(jNQ7m(Ockn>J;G8 zN3%pDaJ`G36+dgGl`nN79IH*F$T~zRoLoaKR|qZe!0ZIIxvHsq&`%|%?EG7D9?%qDSaw(-a*aq;o|Oylz_bWE$ZduYr!Np2ogzw%?W>L35V2Ps{+oe z=JEG}mhYEN%#C+9y`*-r?vXDccaxj!`>hvWo%^E~a|#y@>re%>`#$r~M0cp>58B9F6Mm5Y*GgsZ$dSIkBlLSpn&R=H(u6&S{qN^B>?k${;1by$NsX6E3t==nmX^ZQ zi#iX~!{baB0-?Z}kWkCEiw$wJ4Ml-?G|F3NiaoaSk8q$0_|xPEDw>NL3$^QufFKzl z^e80}I1(zUxrAc`nVaad3rm@;85L|MKGR-jwPOf3z2h(lmUto*YhfY86+XRmW7k4?pDc>k@KgZ>D; zg1oR7uZpt-HxKr0N$KS)bj(=d5TjAViMQBom)Yfij>HISVTa(Am8ROuUwC>z zdU(_V4gJ(8h&bre@6&hDq_a`wa0EzS!p@4`KI zuD)+a7_k>9-UNT8t8zk6!eY&-a*tdo8+f!Fw;Gw=-zfCuRj8w8s>pszEg#ji>$iv8 zL-oBC-iLvdSv*z~X05;*9%W5&+Ckg_GmKgh!dxpGzwl-o2LOSLo~OMINv84!JG=5G zZlef0Fc&!^%{%Ru3c1&%2mH~KMz4|u?By##7inmvJn`gG&Wg+ObzjfLwa*KC`&@5_ z13vkH0zeBC97;xsE8YRDTaxQqQyaHS2^Zc!rc*gO?A> zr3?xr2<0A@&1{{ntMh$S9}%APq_3dqq?e0%l&-j-2_uNQsbe3j1;HhrYOb?KN5;wl zHRY-32ILTlt5t{tSmANH9O`M)%VV5sl$d|F{!$lo*hSp5UyIwVy@dJM>==V4J(p2y zQSkQMd>Efv>JiC&{4)!g^gK^WL|?VpJDZIQO0%w}n5sCj8=X<=GRoxi=Xn!x3HKp6#sell$$cM40Alm zGz)V)J>(mq3|<{}y+}zhBBFbcbLj%WPO9qm-UDRG!Uc`f3(2(gPZ=)nXo~gch9mWk zN-_Cmf%xQ{Rh{h2v8f{8tqLz&B`~!GaSsK$9^&^jzXPh=6I=Q99p+Kb6--l$POzxM zQH7p&?{22?E|}d2>`$2zzo{db?NXlH`&QwlG zv53(|>#%vIinR{N8XWK*dIx2gl0<37#_#CQbcOWe__0JAOtTSVF>St@s!(vyA{BbmMzViPmL%GLP`Z#2~F*zbk$nVkaLlA?9UM+z;uHW59yHzmLnZImxYyqBhj zq?&wE8^X2ZNJyMAxM#5$|9t#CU`41tC-VV7=c9M(&Z~Y~sRjGgWbX2%_DYajLV9mC zv}Siaem}Nb6vjWwW|^W^DbHFaDZh%hDzir3+t z?ww~|7xhDlvlhr}F@TLk>F8)?_Mg6N+Am zv?I`3Gfp1II@vAA1hPkuQ_puMPpe<(Z&cpiM34wx`8FLMDyBDh@hSjwrS=W%!#!$@ zs_WG3FTAEPH2;+z+zd7*zmr~qBpcQzK||My*vh+I3o$FN2lEs}`kA2)wo2-F2d-=$ z_=1xr0f0Vce_VHc%sTP&BRt8u(6)kD-VW990SRu6v>ih6vc~+Xs?*Pdh|ni1%e@z^ zLhqm`E-PfIavQGDxE&EW%%z7>MK$4|!~tI)_A_HRt5{A9N|% zvnf@2>of%fH1f*0Vx2++$wRz$N=0tm8IDbMs#rz_2o7d{0BgR~lnH3`DzL8D$QxnB z9*ue^&TU+%9t5f*-gN_ARzxH(rOYOPFI-#s=iSz*RE;#nyPOM~N6^E){`!GVv5XCI zZ5pSVM7|Z2%;|RK=I#=rffcf(7|Nq{PI$Dug)y~x;iZ%&ZzJl>)Y57v4Nf+5AT8z{ zg5W6gx??`?XfZJ@ZNdjyyk1 z6aODyZ{g6?`}l8T5rQa4GrDDTP6ilAhjfE5kdA?L83+QSHaewCT1EBi@vzVq|_J%7R3eeSap_jSLn7vr9hMv6r=A}Pe}$i!{5yPAqxE#xy%aMBjv;)&gJ z4}E^+4t2)2_eo~XI;GKdH`PuRC_Ipoq#=r8)Hk-a{n0>$ELV0xlB=_-i$-XjXvV8T z(q!&9{!m>;0=vc4lO^9;K^Uu|>FE%JU$*aSwx=vmj@X#@ej-&phFz6iV6jfJwMKIp z_y}Xzq7eTLX~_Xdy9}g>N9%OF%EMgM{TCvrF|3k7kAiiiN~Ey6n=33aqnUsWW_|(2(_>j>Vxf#spEn$oHP+7`AEPW|$ zI2dBMPq3Q_`JhOGq4j1bGdv%D?sHZ(gb^#twVvXNy^BS4d3r7y#b;ZDtQ@*1s!Ac{ zkAC3T^#F|P?f#mgPxmV$D$NR>UT1DLCcqjykZ zk+rSW*+o&I_bbn~=SD#4p7IE)n+X(!GaL6RO+xJ0Iu&Cwx63 zc_*%Z3mA)BlfxAfRer}Nb1yHreqv|5=PsV8$b=f}HtRKTS>5{76#dvxCyIV928+xl zt5Vv6O*i=;w@&mxcW$Y5yl=h6L)o#vUYPY%8;@SxVJg?52W=|5FbXJt3&_c905=Cj}$Mzy3!4*tM8*7N7x+30(48Vknynf+76=3W8 z?LT#D0bOcF-ZcSv9D{X+sfYZ%=lb$Z7nqZ@{7^o zheR;{$>^;25!-dIc*bUfK+4;AvUl|kC_j6hP4{C}&~f+>YJSO=;Bri8<@RSNDB#Xk zPC6KPqOgC*-qL;T)2`}!xQV^D_N{Y-Kx)7l7X4L=w>D$mOX4)RyKWQ5Vl)}_q1v7)ARiPqK}@7M7aE!e?|xg^_kKgDkEl9xtF|&YPqu^mgCRy zR(q^8Y0ODu?kmIZCj*F%fpRB*gMU7aY2_6t+FXU#{;+`Z)3t*g)l3qH+%T)oYm^lG z?Q?C8F)cJLx3j1y`aJULrWtzB8C4%6iN}et;Ou#U2LgfJX-QC-Zw~g-Seb`;Hq{3) zCdziBzMbo)i+5>>$`=IYo4LAFbIm$fcE83~bgKO0db)PDx}4vtZ{+;OUxE8^k!;^ILvGPT!hSOE zJ1;iyd(=Hf{Cp`BVijfG7~~OCoyzm$xDCUU?MS6tCKS7%0O3YTbaqQ>dF(XjK{4br zIpq7@V}NgxhA!=}aj=im#mk&bf&hfAxHtWH*FM5YZBhw;2W;aUE7tZMSnaVymx}q; z`9`YVr70(GqKDdre|)%`Wih_ygVfrx7iv7*qsO!$Aq$ zMDFiqnrA7hx6>+~0acF)O%NrN>#rV{bwrPYdxPZLS?8W(4b<=FZXTbf?#*?v?=Gzr z*yOz@j}&%MXb}$~9o%%-KWngc7ng@Q>dL=A3o!WlMLOu|> z)XyObdhoWiev?x+gnv}p7N>E{we`ah}x8ov*EEfK^CsY+Az=>~su$S6~S4rhhWNq|du zcPKv3yLHEWZ{78SAn#)@n)T_*y`iTH;y)X48=-)PTRYrJ;DSi@0^m@R(DE>=@4(Z- zkrLMeoM=1fe6+Z3(5Jisb`?anGRru4&Z5rwOrFlc=UKQF7ynHssb^KqL>7~0HvE%f z&XuR6UlmRI(~WLq_gHuKSKHV1k;&SO!k#sFa2*fRe4fBgdK+aQM~pfnV=5d)zvQyH ztIix$)Kn)8jh%CjI_=0xWQ>WZ(|D(CieT1{SFLclW+-vQgN|vn)aIwV&cd}n%6E_T zLn}QFQ`*sK(rO>6QkA$m;u@Jj*K~bOtm%S_9hp!JTS9Tle##-;Q(T0H!+xvjfs(fe zD-ypy@S)q#F8KCF&|mX?S2`%uUOxTAEGz%)piAaD8v9utsAk zW&`k25pJrRGoE^9iOcMOVrW-^x9Ehc{&hnZHP3>9F1;MOK2S)LX|bTrMs@YQq^Hd=bAR?0VL| zPm|{OQ_K3Xxuwq?8d~bM$v#`YdF#dc7Qjb7hCZba5qFQ<$;qa(pQ}#fO_?jpxFi zbmy0ogwa!#RTGXCN>0)3VVCzD=?PReUM&QFY|hH4jZmg~t9aEiYMH$+^$2>N?q{jw zfEFE{Clbe!#NWFnBaX$Qv(n*wwuUvT?2J#KBc`0G_5MliIo^Cv=k$A^La+#dcIUFH zYx;goet^of3s9NtW(i9vK<_iA#W`w z9@?9=AL%l(U9UtJEx)6S*u*u}5j9B#j~Bn?)!*f0{I0kW-@Mh_orB)h^-IS!)t!<^ ziBdaP2*u*3mYmzn~9hVl4S>!lbN7TeV=L-4||2> zJAC+blo4hs%w!-yxVij$0o>{@m!%tGM=U3Eg}_~o9oX;mx9zkzvnMZt?z%$-wdPn% z2)IJK0sxP`H%M>vm-=#QdvT`kx6;$>Lg{%S7hNguRM9OY{)4n4z&<@5Zi=GRnP^{6z=o=w(El`yRvoX1qZfb?Z&T+{ zbK}3cARzup(;J_ZEPV~U8JvVfb=bMKE84EV-wy?D+BC@^_R-c|3~R-+HexfIC!n{! zHYnQ)CUm2`=GosyMxezu~?ma;0 ziUSB;HDk(avEiMb9p(?n_6#3g#KS}Xrw&t%;Mp&iD*Bg8^}{_{GX44G{khOiM(#U4 zfS-l_hR{F9=fnugu~o5f`RVz50(itPXwaWcyZ=zdzU7yE$jY*E^2TNM@#8B|*@wmE z#~Y%wM8kK`tZB1);a=-!ZpACK&-QM#%BpL#a8cSBjT}~on;%aZMdn(tDxLA;1*&ER zx1D+p8{L()U$h*II5sH3j*5sseP5Z|sa>wI^sM)Et7HkjC;S$_*CjI@Wf04wv944o zRobt+F6PsuroUEMqmQSqOYJ(h_5+7wwfi|W%%W9Gds~0v+`FuUgg`IHVC1|m2-T10 zv<$$xlOXXAhO|{I3Q+!<{4KXO-HGEJgC%r7rgk}vZx3#v-{CpeF}qJT)0Fw$uDxPI zU^YNPMV%C|`j<9VN#kzp%$;gR+J$OHfDbEpIlBnt!<4?|)B`#9+pS0kN{xLh;}!Z} zQrbX%!i^qM(wMk9=9K5oIj*Cdg{C?mff(R8qCbXIm3m&UjqimvL!i>}()z($`|6t1 z=IX!Qw1mIhv|!VqGfG-~C96o+*nj~x``R6+mkqts`UaWfH!p&iA@Ml#ACWHlAGIY& z0!;oJ#Jmu~HwusZ-y|{jx19BFl2}Lz%?a=MspkDV2`dLk=a=`hflK91|497dcbk8U zd4B`r--XNG_2%=l8#vgW+UC@exi1*| z%6KhCt$X=|9oua0wVni) zaQD3iHIzRL3Ti1CENm8XOcL;tQtzD$Npj>5M!&(nCX`x{(tI`B^iT~kp(Dhq!`kt} z@1fBRk8c+-tt;hS)DAgFyJYU@G^h{>#_!X*5>2CKmHW`EnD(32?JlXG)OE0a!>F=h z(1&x8_Xg30?(d~=b55ZykUV;A+C^l;MTSf+To=i1Q}c;_j3t8X*U1Q7|JDy+ti$q- z+i+}%RxB?)#OL+2zc_R%okLZ)^%JD>(|iO)zMiS0PQAxq$lz-ER^lr4&nasBYl`w- z6aX6*EnHQhg|3~JUS1_U-=}F*AM#3Dw{>XVW#xv`ueOh^M(D=376wsSwM0BlCW7CI zV!J?dO=%a0DyJ)6@{JZ!X}z`I`*-Hh2{)ZOtvr#roL%cu`CpgAe}0y>gvY={kdgm@ zi(pK5$n^=Qx(i+i%>8=~w?kTL>ZWEv*AAXB$wz2yjct%=v#+ht|Hdv=i1>TLKp8*z zkkbDjAIhi4Vugl;SRExT@6pnHvunDxvv<7WNfTf8bM#_8Z0;s@J)fX9DZozxfMM=4 z`9J5SlrLQOHyp!6uxVVa@tohF+yTJk!1-tQ>WuAsU1C%Z$7TKs5}Ib@wK_`o3(8`L z{*U3e{yi%{hu;evi$ALPv(GO$=`$3#5&Xx%*(;VL_`G_fA$Mn&*P-fIDJxMwJW%qz z9Eel@Kz%!ff&7@p{%=|u#fEYGlZ`3tHiac!qy6(uyVeN<(t`$peb^P4TlC zm`Q?MLbOPq(o_Q)k46P*AJ6Z};h)*d5UR|kUc%P~`_=pmZz_7|!{VPsKk{ZW54MYn zH@(-?pJw&h#k+R}b{rqN$ITh!Rr_Xry^}1zwUuwMqN(G9of3S-XFU#Vl2w~7%N7G~ z4Y{tnknYhGdQ#_!ZAOP&rrK&{lx4iu#9*wbp)8{y`;!BS^ao#VsYk=Ny-;(u$cJXl zkq%UV-T4jAr3a@rs`3^v>X^$U^-?JZzz$f+-#`fBg@|PszHnr2wa9wr+#97tRi_`H z?NwTl-(mwrN!GS1=-!$?ISt_9!eWHkeIQB%Ek(!rz)ls7f&pxJV47CpLk((+80|GS z`64ORfD!-~-5`pk5M*Jpc6?pBc)Htrg(|y)BK_vL+Mg_F7`wVZNL#$mlS`jy5ypS# z(g1ot>)cKQ`QX!)e`ewQliVDCV_gMx-FUX$py997un6NR`7EW#+tYi}*aNrHFq>c^y>+#j3dvb##rg~4QzE}Lex|&~ z(WUNi-*m2X{207LCy}y1%|kUac~TlsegX)qIoG#Nh^OhPVwH&*N~H&zhBZ2CKfVH` z_C4}!TuG0l$Hv}B6OmtGx7^_t8?z}TP zua+>Y3PU)>r;lvCtDSdMh?(nl^3hFI=Tfk-{!TCQER;3c-M2p`(E^23=sFsNCv)W7 zQG;|Vw9#=ah!=CtX2q?dzI~-* zm>K<+o@+Ta4=+ekQ4B20_O&ja6I2m+mYDM9%EO5niGb$aVUA!w6f7PRn&Nf&bGcob zuZY1^@H>ZWSWIp+6X&^tq3>Zp)7?Ne}D&CAr+eE$l9K^BrCH zuvDK$1eOjP_PJYiT8hpc)fMKfRTmOHduX$+hCCIuwsvrRI&Kw>6w(ISbl?R84S2@l zwXQ*Fy@>C!h6Yw3lCS}#2rZwp_o6lxx)s~$YT<7F1xqK;=6FkBpX;11o?YX>{^rP8 z6{=?3Q4|&9AOUYH_)M0)ivWAFK}Z6%ec(<@ zc2;If%3D4s@tK!nG})Tw$D8gBgrG+j5zgkXs6uQ-huwDe`w=-rc2O`kJswb)(n9)~ zT~|8WhevekpX;OP;)b0VX0{vz+$=47cJ54_?bV0fTKF=LDCfsvWj5Fk_&5Z0U3b?U z>sUXyl#(Y?au~AZ)?w$$Zg2Q9XFxfUvRTd6H-4tlC>*_>oE;Y}d!zrQKJ06GnEU%} zhO8nA_E`wL?WIeB4)9uaxK)^e?n%b=!mwJg$r32y1$Xv1sCl5s${R;%uD>U1q@r3h%O*p-gK6mQ0KKHGFFXa<*3< z=?c<}j%u*c=f-#k#D_~wOD`xt$FRz=P|QwSxJ~sTrQ8MEU+Tnyq!x+&O7woWC8fy0F)qt?r zlVC^xXSha?eIfV;T#?S|@poj{+#)JmT^wotK#P;BeY&+^ZZN9=ixJH+Jd&A+PC$k; z9YBr`-SEv;F~UiTeQ7;(jY$bUuJN^suQ{xmjtnFivK+VVGwR2Pu;LYF_w>*VP&~Rl zt>mSKJSt7pnn|?R2VHvc7S`7LB+J@ii5CsYw$f#X<-|tadY!j#-L+u8adD0L+w;4s@!pS-eNj|*C|y*nu+UO- z$5;kS&li+7aM5DfItc;IK(lCbx#g~HA6+0V-AGpa^$B$s)jK&~L0bVz{Z#Yyof{S@ z@Rxh-q#U8grL8%=Z-S`ISlewnikYE!Eor_<$v^Q=+h6zxi4SX?TSmxtPz|s#zXR7b zt+uSBEwNSZYRsD zU{Fg($0-LbE33a#zYBliz~SvvymfkFZH%U_6%a(M(i6c?dlVIuLvFu8pC4?MbRI%w zP%4d`EY$ORAQsEnh^;$LxjX#o9lcK zZyIy#4Bu^{$s>b92(0w;bV*XXeTVs8MrM`R7g^;s2aGc~RD~t!=h3HCj1IFx)T$yG zO7f?jIKW7A%q??E0_o&TW-^(1!&17FQ~u7wFEvc0B<;Oik2(sklX-G(P8O}vaEwNP zMm%mi^RD=4chjBtL>Z9NA9i z1;4cE#6>C?p?cF3d63PRe@NA>h1Ml8*>Kc`^1Vs ze)D;IkVu;V@|{k^qnsVnnIgCv?wF6)CqbmLWppe-&!sJY)9_2s>EQhnMB+T*=-hLE znbU^SNQOKHKQl-7(S2l$2URJ^gx$q`-Ji}qQOJCPoHx_LvysL}bCj?7X;9Fxs2|#k znkD*o>*|?Ee^@-!+ecRDBejD~inEM&UCl;h06M5kRB_)#^IT-`I4^5mh}by(n3Vg~ z*@qL|S815_cPS_KCkh=BDP-hcPxKCU#xcFad681kx;iV?@($V}*<`I6we(*@JkI?@+!>T7HYQZ(`OTA!Z(aKqfX)MFTthSFaAcdoK5~6QFjqL{ z>+XtFmhEz>Q8v}a%Ut7JPzva*_Orb=ZeQ6dE^r?qQ&n$QcXVRYsw_})9}#Q*-qnFG z38?1ts+_CN&ngkubz!Q)pvNELeXl8HvM-q9x-cYZ%yA3fW9iU%sU7@uSEQ@NpU1wR^e#jNj%!(<|0PLYw*l^Fzq zg4q_N)|#(No>kBgOeWGick8gG3!K{;Kiw)=ujrt5sl^4~%d71iKP`Ufk|5t6+b1%$ zx_Y*ohoU!6W(AqvkY->z>_t(0RpprzbdzJc$mT6U{rtz6sj`LyVk5bDv0Lv5J2n#b zI@FZAQ9W}3{?AnDT0QNlAq3(pGC3VD_5JUhu2k6Q&Q*3~_5q74X%CS)JkB@MOX5J* zV-u=|M+0z2SS(zTSq6PF?$_i*gT`DUa;W|Z0|$~hEWKP%N$J)XI@iYH&vX0H_SdUV zR*>NBXC0jTS4)R==Jg(qzRodpay{RDQgM;o8+h4VwFL-W3GwnJ_Px&QXc>+Zuh010 z#B`U$$x#^8UrF=v4d9Wlpf*DqYep_m)NC*~try&532&FXMlb#YM|=Q_6bj*FwO&iD z?eApJf>HF(%@zSw00TJs6*`?O^Tdn&=f9k;ML)B8xe;V-t^?50r;&L_)9O8f8mf7@ zJn9(fMVcs8>b?xYj(Kqv+cino0Z{?xnMsA#7rjw+QiSUPDzKT>idq*?DR7@P%B$si zM4Wcr%k*s7EPJT?pkMmd+J^`1(@=qRm#R;Znr?tzp!4K#3=0ZYI0h|4-UPZA2KE*NUSb%h(slVB_ltR2(lvvr zqiViX+1B{(>=)bQdQdi&%WEeHTXyXJod?E=wJtL-s?+2&>D(K^K!4!p6+ zbGlK-KT#rzfh5UYZ<3F$!@p4^xF!hOFZAp1RCZX?nv*EYZ>o+dh1E8;N{jN zvUTH*k}><5GK+~ntl%N={ckrlpNL?bZrrW>GM4+`rD|_%D0l?C*o$JFn~lODY(5@( zJ?X>}Mh}%<0e4pc`8xw_W&3nENg6saLE@dP98Fn!R2Xgi6BDwX?w9eon`}Z9^7CnT zif(xaW~c&fSSGENTJl6_Phew47%$f+B7NAad(uaY(jv_#)=jdzJgwvOD&5BY(QgLe z&__k;UcSz{QqPY3qz435#fg;KngtCh4ry!;cP6%q8C04ZUFI+~V`}u`x{oP}UEB6e zZ@x0X+AimjoHfoU!F?*ZEDr1ZZlNu| zMTw$;(kM4vSr(6hq#Yc~2zj0J-1UMNNk4lffU$Ua>ssu0;1gW6%9D9AcITNTePrFV zRcHNP_~&Y6vVFKxE(fKl!Fr3|q>Xtq9hyjwzm=-ImHPU90XcB0byYBuk~p}qQx~!H8WtxF_dN@ zO_xnO>k(N9Ya5WaAZ=6RCoYhc{DEUSursar+XtzI7E(Y8_e>dc?Yz>4R9zR;)dJn6s2z_Z=$G%;a#trNAfJixH5 z0frU)U}16{9jUWoGGWo7NxmxgUb81+xb-<3@EE-N+~qdn%3m@_=wDu8#w%aBoj$F` zP5Ee6QFFzTrVD9F=OPbUaw9;g3+7fN$05X}_ zwm?{l&y>lK7ipK6HX{E_Rp9}-t@NQrAXPPQyuagw;#D545cp!)D}q}X`#ohEFCaI0 zVvoS$rbjMkb0#%8525G$i7oU|s>OeP({fhWc$#uFA`c{r|FX)qs`CH{HoC_6y$hTP zyF`)4jSRk?TElRc!=Wa~KASC^`rX3y{C&!>rX^d>sK7*`$19iGR4=CWrxEfl;(+{$ z8_M|sPUXEjm(izuj6dL3g1g=Ch!?#P^EJ^+^}N%)!&Gki)u z9i7k@i$+)~FXDq&)6~D2zTC?x-v#@p$5E$iOjxuNLbl4>9wy?ydAT<&TfBFjSKSM@Qr~52_Q#%aO+J!Ne7CZ zFzB_c(0+Rn37TD>1ut{1s1k2doVlfp>XR1B)1^!TgIRk440gN0^Bkt#Q*OKG|GlgZg zYC$=@WGoAk2Y;Nm2vM3_Xf!5&KOJxzkC5|$rz;`8QJwk!HB$ywP-p#NXx1N1*H)Ea zOL}M0V8OFJqzC+5_7^S4axt=frHY zoo+Mq$T*-!#q|9e9$O(3wE)Qj}C8czO#pbiU<^Q zxbvRttoi6J?+<7e`OyTZh%MI>z-QgnJ1mmpClu~sJ_1a&*xhU6aL9ktVq?NA_Km*0 zsPs>5@Du{>9bar+O zIeIGwX-x7v57_l95BLr9CI-ebPK|eY7{Aofck3)e{J)5GSNQPD51i%yFtzN`ZJB>2 z)KvaVs0C&t%_v0w%2%CQzF1B#2SwPV6pD3P0;DUc46g5fn4AO*F8?b92fN^D0PcJ)D)Ps$KjGr)-oSSeX>-UwnUYgD{@K1PYxYGl z@gFfPW8fIf>bEaO+%JDI;uH7(rOFpW-b1w00+!Sp;m%ysFE8}54DG+uXTPF+wzE&%jY<6y|HL&kep+#DnOV*s)qmGk zlc4cQRZr!`A&qi9l2uw9%)UQx>WZo*Pwu*fA(lydq@<=*p5!2T`z4iyj=evxJ5A4V z-z=aOLdI)CD9j2|tQ}~wi@^0jNy+Dbgx!Fc<;j^#=3Gxgka(@&4WNK)#c6)c+3O54 zly+04U9w4a+eH9~e2j+U?=Q&lMGW}uEx}^4(UAf*9cmwiU;9`qikG#zcHDuzb`zNZ z(r5;o7;>G5Hnp8G?KM5Oe&Bdihu?#6=U?+WHcjqLaK?_4U?P}qy(hkQ*q0KOO!ejM zt?*G3$mYSh_Y@MGWxQtZS+>6aSzt&!_;Rn^ywBOBvJ*GWUs0w^MF^vhD*{=<)Oi3H zc{DMh*2?=Xaks;Cd@pEHk!8NNOAH4zVf1mo+1_yg->`xp_O9SSd6mYm5K|g8CyZH; zB529rJfKJAmOvPF4388;Qons#h>;n83qLELlGf=K@Jhp~q4VsHQT=N_?sGTB}?%<9slro zG43I|q-V~Z7p|8$`Ce}JbQg^y1yE5WW>*-ixxUm_w;7YSMkl{(4TBJ}))6mmjw?}+ zK>NF5o?guhi1mC!SMzmbk;oI_+4Dt`+4<5F?%DSlk?gCS-$kcy&`awsXTt4WAg~Y& zxPqd*RW#>u!`9T%AgJ$vo;RhSw`f`FRdwgm!pT6T1qYJ!oWT)R$U{&)b?V2psCQzF zR;`7#Qdd(AT{S0gLbYhRH>m<;T4U=6eYk- zCGq4(B7yoEchfZAGeh82#doEof#H}Sx)BAv&-XYci(I>qK;<`7_rsDisd9u}9SwS{#qSO7|<*vsQM?pip0*xk~$MhnWWY)3uS-PSQ zB?AWvRQk}6=}v86neNNf@!GaX^=Fxz2W{lX!;!=K)Rn3lmPl0v4zWe;R{ONrOX7!v zNHCHEx}CD25i@TT7SQv!)8)+%oZ0le>Bp(9DwHO^wvV&TgbORNI;;pm4SC)gJ9JJw z-3K+ReY!_s!!nLFzT`zLxNnMMkNv_g5xGR|;f4C&P#dS1KgcUD;Jg;2?fNlWSq5TT z5C4HvjPs3P%eec-0~fXZ?P1Ds?RKXfDAX0J$>2i9kg$=r^OfAJi?)-XGe{@IlIg^~ z-x3jnu#l7&!=|U+e+kKcWd617doah22(a%`*-*v~6R?BMZr`V_*t^l);ezN>&)Qas z=9%;L>mC~(@!o(y%%fXWqS}hTyZcuy8tp_~P=WvsX;b11B1z8$l!AfgD7Ly^7RBD( z4~dlWWtSK3VCLZp;0UNKntwCylQy7_Z70Rv)hNXv)0=vSLEa^cSBccy54HM{p({v;5mkj%*c!!s zYs#cjzRM=XQ{Cc~C5~@o@1*)p(>?dvT-2wH)N7A!0b8=j%o{!4m$()%l;^M(&Ku6+ z8fn>&bfer6pLik1$Ldq;gxRpV>b&jU_GqWLLVuVAQtom4bIc+XM0t@&KNr~lmTfJ;>3d8a37MPfm6X- z6mEQ&I@mw4Znt0+NUBU|{L9X<^hsselF)~^j z=YZtRWv`|ArU@jwPcdfSbA7j0u5iQoUJED$@hGry(J8WD+wNP;xoQjMlpCq1pm_Tl zo1(_~ykxikF(JLLMMZ}o8IFV=D80T=RH`!4l?!$Na(T-l+Ok-=SJM3+_#B7oi;li= z6=7km_=DuASD|I;-PS|q0;ZjXPQ-FMEE1GZ${Hqams~QK31_#L#NO@gr}wl^d?SuRSiyuyrT7fLtL}% zq0|hRsmi{_ud-rshCgkIOQJqi1--uJBYMo_bl& zeJxIq_6N>}NiJ%?cl;gIK>~p|ma}Vt%ffPNmtpnb>xn~pWuO2Nd^g%r$Iay}GzBf4 zTLkjwJqxegj)(UA?nj zxVc`@G`u)p18S7K6goSEkYA zns^vg!%)3kSS`LS6ajUmWF{0{Zi-qw-ORUaiM!QZGl#uHlH!FEf}w}(6p2~5e=%~> zB)9>hJ57@~98MQm38>v2q4mrb4}~^ z{F>dPiDqFc3yeHLSKLw4O$HU1qd9FpV(7tF#Phr|k3scHZcGg9c?(XAqY^J(t=wi_ z1Pd68az(0Ndw)&+ZOE|)(4w0s{sgFhA#nn-lo0159|jd@cq5`shhMM2Wn5$lC)wIy~Dmi}U{ik6y+=^81R%)OSqp z7D!Rn|VeR2w=-T+PrfxUcVJ|9V zJ5{@0OS4!cJZhp~F=8TpkKJ{0-jb@Ep&cK@WZW3mGoE@AJ=QZ7tVLWiiwS4lQ%DdM z+-6XFy;IUOvPG)3Spm7r)DWSMDGnlNd;Y=IQL=&3tw*Fg;^SrKl`c~88QLhaE7(Zx z>E(j~v1EkatDBnQ*jh%T3Z)mnp0oWMR%GsnLozyjL9TM?p;8~J40P?ACXz!#zm6-5 zKjBj#(wimyEvdBh0NS23CM?@m1m0jl38R|kmvKDBo#wal*Y;~A6Xv_rTR-8-Pirj- zZWdf@X+^w)+8hfy=MTiwjbXt)glF{8tL|H?TM?TZgd=`KIm8?jrWS?ZC=7rUY?)cV% za#k7zzx-+{oMwWTYS@?pUo@J$h0wlx)F^?oGu>SwMqiF%3DxUxxI2#xE+64Oe~+GO z>l4nZZK+^Ex>00fU9Md_B-UNUjCWU795)o|3&`yV#?Ga4+qJoiB*wd<*gfYc?4F>{ z$KFB0OCJ|9_jdtTPrg2fa79_{yD%NK>67Iw`dZR<)@$fmUuXZ+J;!Osjf&avZwOg6 zQcn|lr;o)cQn3|dR;`YdtS?sVULW3+4w&x_?~v8}l*f1O7SX0uerL`uBqx_?$|gRSvw$o7Va@nUaJFV?O^n9iaU{#a6k8sqXdN zOsMTyqy9r)T}DjyufD|rO3e@aOMwDHiYCpc;p)7(YN8Eo>$xorL8|$4(yX+6b?W1* z`@OeX7@W;V#v)0IoU z-uk=S!jJ20uwk-_E0FSRV5>-77O#e@=K7VzRbWR%%|u!s&kLO&IDYc~Pc!Dg%a7Qk zKITp)_M4AAzIlCBH~CKtEy{e`+0zYL*3SMLuaN_4?PupU89=U1jq;Z~frKCxR$S!$yFm*OCz z5Elm_RX^ifyn@i`-hxmeR7wKf_4Ve%*M9sp7=ms{e%Pl>@*e*i?}a{ zrM`DF9UyYLX(CVimr3r!fhtu7^(1Fx*;Va&Q~-^oAVk6Zr#0)245!4FQFTrvYqj7{ z>7|}P=C_~HOIbcgFF{$s6z37<>w|?qm0l&Db-x2bn|MF%RXqN65j+j>PBIQ5VJx>9 z3As;uQ}B8b(&AT3(H}tF*1=f-&_e~a0xAu?SdvoW9$)!t==AA4sji*1K(K^s%@%>k ztJt$>`$YDVNzN)X=Y!zyoC=_=;{qAyu>r+3|1a$D)2ewpeRvPL5^wn$FQX+q`eKZ@ z>zlm(v=_OcIJPup#1mxpgI7)DO5Q(>azD$k-r~L00T_?O|I&tmOaI9y8Js=){&(PL zyw>&ma^J7viT{+;t!3ADg6Z&ac__KdiIAU(?yMk<~mW% zb1|{2$qq-5t*)Wu;cC(!IBTUI39+P!?vKwOlWonMxe=8>%4kc%nM%h@5ZFS0yYv$43cR9lJL_1)`%SlV+9Za*9SIi{(JHLk7r7cQG3+2d zgJBc6`sVdS{k37bqm!jIi8m2Mt_l|9X7eW7)e(Hl@7nIWC(O7~TbA$W{HpluiW7g- z9u+B%F21Pv%qYTb)(2&mgSRbUc=;sP@zU`WU_iGCe(D04uMY<+9lxE%y~j3$Ag>(8`yruO6#b)ubaOMIWsZU&^; zHZMKhx2Hb;z?mm)A&-ew(gI(HWEq72j*~G0|I)C<(40y&qC_E!mfsq^%Ya62xBs7r z^}z*VeKPJi>Xz~QpPKhqlJKAZ)TwPJZ0c|6BL~%m^pWy-cwi@oV6F>AA-|Bv_6BE{ zVmisR|1qu|OAP#C;x7V(ra*u&=|WvN`<0mPGYt)ZB~*!cH-i1Pw}X2foF9t?}D z9QxT2Z%#)LH`WY695(D3Mdk8pG&{RKOpV373vzt2N1V96Wi>wkm+kQ8AA^UzJpUcE z(L49_Hy*umPIzQ%Oeg_z;!R35j2Nchn7xx?k$$wj9^qouVzC&c#bY>Kf8U8>94LdU z7)-22wA~8gYIwAuAzrWg$h0Ib`*$&4>#t(ID&XE^O90*UsEYYslNm)*WvDdQL=X9a zGZHX!7B}t$N)$9VL#`GT+cLUvIoWQj?|7F;N`fu!1kChE=j`_;+dIeHdTmj{_r(i) zRJVXN8V^rvk|q+kuXrcygYI>2ch!*FG>*#~9>6ETxZa7H+=SD$Kneo;DMtSf)BV8v z|7Qh6c{d62el#Rhq>X9veC#WxK2SiGn)ik}o94lH-5$p&k$%;?R>}AU>nZ1}ePYSx zUo^Ua;*g>@quayY$MMJRL+noHiHM0&;)Yig*IUixguwwnTfRIrw}hgk1;JQ#hnwU0U0S3oCDhNXkW8^?o&PatjPVzda)EiovPSp$!+^W48K z4^Ci=V`uByjqfv^ zAkwxDEl<9f%k?=Ke+;gWXE(^y3Uw5zBn$L}v0e?XkGqb2WqZSq%QXDf+=1CTHH9XX zyw0rTFsf=81Z=Z0nyc0q?AAk{cy%(K+o;aMO7=ZH8|^lWixGOqz8p2xU`P3@T4c*w z*d)H=gS+q!wf9f6SW6y7UBt@Df7ub?Fnv#sx#D1?H>1lUl!w*om<2 zVQi0L=iK5H>2%xjMWdx@5>zj2hq+1@zDs~_ot@WNSDr@931asVh#aQC*N!ZcUJ}Oe zj<_9zQhI!#um17RBN6=D!^no;wsiN^3!9%Nykq!S)Boih)I5o5x-$3frwcIrZx>)e zEm6%`B@36b8Ixi7fr)CfkxN6P@JBA@PhbZ@g9|;TUA@!cZ{y(KM>+pQN8bEPf`|$I zXU?nte-BZ(?W9Z%L&2FB2oST6H+X`t>GAn9gc}mSOBG@vz}h48TSe}P9(n;VM{l^? z7CFAeAPnDl3M+L{B+zM5-3*H0+dHITLF-XFD;hJCjf@;fLEY;bpBaTWaDY6`=Q>0m zkg2|k_H~)8nhv{D9E5=<3&gvo+&u8n8@Ph33)M|0_jl6kBt%tab=-cbVN6ZczR)(? z9I7w)NI{mvDowY{Z^|tTz3XM6;Ar(QVEM8>cE_W%DuDOd7Ph4tfHiuvAj0h<69D9B z(zbmj3DJffAAIw#D#8%Ksw=NWaE$>@If^M(d>Dr61=VwkQ@?<;94>*+GB9hh?f;Lh zv+!%Oefuybpor2nLAtwTgmibONP~2@3Ifs_NQcthodUubNJvPG5C)9yj``mDJimVb z0Y1RC>$-P-&*S(8wU??aXojK^C|Jl}3+)}F%_^W)eO!Z@eUpmxW($BZ;Cp9?^m_&$ z%N5s}zKiIOVj1UGdI+shZJS8#M-X2TU*J4iRGB1I=2pgjICdK3b9hsa&uIc!Vi_SY)Z^SO4505SvQ0^(PcYsr_Plc>PQ7`-#MzPSr$-!s$ojgc7gg%MqjgA8O z^}NY2?jm^>0+%UDnj86H!X!^B;%H%H^-mgeXbqWwRTAc_b6C;e+cyDHr8nIT)Ay~g3BHbd+JsVKuv;78y{*+b&?b#LXZzYM3@i(Fz3v%hBm~{= zU{!jSB{c^p=8;rSqJLnwD%ZZB5wOWS1oEXNy_-vsbKbJM$puStflXm65<_s=QH{wX z&qeI2mGf-Kgk?6mr8O7Gw*%*GnDgv#+_II-p80U0fc2X2yEf?zGRo>ix(hIlmdEI? z2bV$jP8>Jt<}lI&ERxyEwnNlK+yE_(>PY+XP+Rp~0oRO{j4K~pg8(Rej`NGp!wUW& zkduJwlUEHfHPFndmYY4z%!-IgGEHw;)2T5l9BC8{>5rQ>w`Rem>_z0+k1+NEHjAg# z*8(zYV)Xl+oKqdS=>k*s{&~@Ct^b?C)K&gy3u?)Gl`KD|m2u_AU$g7-62|CB!|AYF zkC9C@_j7`g?iYqLyK7+G-CD1Eb4I}7f(Nr8006!hVC{+GAOpo1duz0;!N{<@_kcOaS7!_5g zjgFIzYw^KsDFk(~@spM2@#>xJGxRLR>lw!qC4cx&qz=$>jI9d!Nxax@5AStrdm{)F z6Hof2Zjpg#Oded606+hk$t|lVk2n?MJJ<*%W46yY^F{kbF|Q1gfHb!stcW8|#xdk? zZ4!?GJtcMb5L73PLasBhAN4-^d);`}g1PC%mubmra8p5M{=YDOR99Igy8T?o3N5=c z1g=arvbGadPn5y6MP?@eYbcf&O81Rb<;b~X`1JZpvagn=$nP=(i@{=--+LNi3Al=y ziW6b5IL6Zn!7?|=n#6jYp`#;|5=Kq9Ow$0Jn;8tYH#5EyC?_^1jAaz7;_DG;0E(Ivz)9e8*PU#@>bBstQ{$vv4UIg2hZd*(a)li@_}Tanez>iECLKCzBdOt<$oabFac1=HQa6 z1hJ00VDHyrp4NKAD31FYn~`7lzC~S72Cc479JD8*fA}nmJXirdKnxEQ6Z3Bd{4;KjiNWXa)kGtSJ2h_+7h66y z?YzT7$EW38zc6n3U;A=HU-Cd95;c-BQy(k0ihjbOT5vEk2@y4ikki*2zDD61^CTve zH7O)IviPao<8+aarNBn)Z4f~AjlsAs7vXgKhBTr$` zT{h>$y|W2-WUK-*jII4i+5hz}G>8#)u7}PIU za=z=nf3{af(P)#8b>1uEPfQAY*&2k5j0>2L3$-b=X9xLn*zL8_7lD?@Pr@G{^r#rfuOZgE1A{>$#W+@59a)Z;OQ*#2gTu@b_vZbADr|21Z?e?bsYyCY2Coc z!<`$sUdE_i-et+re{i0=l~+SQ+S{A>vA^y}#RmIY>s^cgL7U=Yvv{PNSP+Y1hzYJV?^RUGDrR z&RH<=g*w5ay-(ZKKVi}e*jSj4!SKWN5RAu8ZfDHudD~}5biK9>*hxD6j>kQ($&O`c z^xNFDoy)XXHm)pYO{gBE8VN5YBHYvpVM=7gwh1&~^6(TwO2;alpWj?PFLf1RF*ekL zpf3Yn)jUJ1i;V>De7ZlBw2=1KF`lwY+O1V^PSbF+1zZ7)L<0>Nbh+y+FOU6q@e~K8D}>y8KS?IZA?Teaxo)D?WGAa?ZPQ z9le>(aib<>CrN`Jc7PkuE%T2Xa4U1xNi<_RulpeC;(odGDv*zweohvU?+%cpc>?>g zIC8T|UX|Kh3kwljWK(t(pGe_y{NO>dBGwYSSNaU-^N388u3kAE4xq zG)Zjw%HcxRWCVXsCj->pq7-SZ8m@6Bx019-qIc!a=exBQj6!mhswwb$!XoFF_G6Ua z4uwmUdkUm_25pXZik$pj;r9&C1_ARKv6~Z4UkICf#OEQ9`(aS#F~;!gR^O3=ltvov z%6o011K^<9s-Y;i<<##&Sg!rkAe5zy?2OL}VOGVv*YO0N54mnpU^XTLvyo$4tk+a`BO8?@8CViI# zTtn$ZZP_jg(7@Yg9iBKs`SC8UTynzI`2+_2KXIYKQS>DvE($EbmXVj?Nu#;?mr{_u z!P^16^>>)V_IH?5I~Xba?01;sp!kn4=W}UEAfer?UFh;0jAc1d`h(+H!&T4%zhf)` zC7)5Ap}+g(`u+bu&#_-5nX^Ud3Tj>O$=7Bp=c>I_yzz9>2^CG5Rc|;G-G!QPOG=jK_L!*^E=o!7ibWP};Yk5>K00!%m)c5G4Lgz^2wSZeet6frsCybZFPCfaXEo=oS*X z44?MTI(sHtBd3%4KYZ_VGGd0Sj|IkcjV+y+WGQR%LPJN5gbjg@z^HBLz;bB&LJ`(+ zO!;Zym1%P!D^8wevGH~G)prV&mD9y+r#=l;8KpH~zF~QAP2_sczq&E~(JPg{X3o=- zOB}(soj-P?7*t>KXySB6e{bL8McT^Hdn>|yF{#ZNQ~wF%W|KFWlq(6gwvHfR^#Rrk zzBjoJVl_$X_gzwI%U!Rb`)a-F2I^(Fy$)wW>F>dzNqnP)_PaV>lhC9;Cqt1b|a-Z_xco8<7_%3gIX-Svn^i%TUQi*q1GLz;zQV+zGAPEXOLUd zV;vZ(Lcfh&DrCI>Fm_r0iyQ&{AxFkjshh$SWbe+6Tg1Ai9D=<32^%;{=5`Nu1V-Um z`ujbgS?r+K&<8ccN*%pG=$0Qd1lX3oYW=n?{bdFF+qU%YG|RVHG|h}<>_T)xS;X8e zE5)|bw8D#0!v=uJ{1yeI4)4hN{}N!REPCHzyDj+V1Yy z$tWhgQzS*oOq)wnseaaYag%AAlWZw~y~#6_61!|6pyVz&URCb>3E2qCRB~vBXWxm5 zS&7K?c)y=ULqpKZ>T^&bX5~V1er=g-NH@ye-CkpQ7p(zQ@1#6HyH z{HNtm6x(#IU;YG9*B2^G!IIM%#thdbIGJT%GdAQG21;7#G5da&P|zs6UBHP)qZ#=D z+dPs@x)r)FXfl3MtE=HSCS#oFHLcv=YVz$;DS;K)@+_#$R;BNZFqOffAFYSsMgqJ$ zIt$*+t;}2Yu=hzZfZWpJW2GsAOij@km+g@*<1qtoOk^?db&5~vCTi<0z(d>uHF4ny zw9W2`CUke+;3}P$V{Q?2MOm+8Nj|Zc*?<2|98~x`gIC^sGMgQdxz|*EKEoU`?e9}A#oGyPo$C9+w)wfPg!}gpIym8udbih1{qq?K{l`Kq}-ri{hJQiR+$u_z1y7D=)L=S zDJr!w8?nFfFFa5_HZ#f)Dv;0S?2wv&9>;q7wKoIg|=SGoM^iOv;>^&l8GB;{QTDoq1T}e`X`A}GgXvpno5$#zl z+JUlYPI~kWKrV%PH>}z8pgr{;FZZ|VuLWC=$#y!!kBiK^#NB|W z;n?8y_crCuJQaH_Dom%SEx&H1tEI!TeTwdUKG30ZQ)egZ>H?(+SBcVt*~E&Bf9AQg zUhps4#VzxXXWWyl_$pOT|4}WLqh3{uV^Z0zWLJDg7Eu|uzQq~1B?5O=si2IetL}C) zha@8QlxD#$A9GZ9@EA&MhfYMZg4O1foSTwFN53ZC4pTNF7WA6sOuuN9?Ds+h)MU{} z$A{kg=z_t?dG991VaJQ~jAEN-q%C$=Zm7;vj)g!F!CqFS(S^e@+=Czq4 z``IrHxL~a?0mgU+Z~scxn1t1 zZaKPwUl?oO%>x&P$k*J0g0fXqgNRK?@j78V{KjRM86`PaRT0p+bebay(F_k zl17>$8BNq2%`i^oXEVaN1n+-Vcc3|AFU1VJs(O=*zR7BRjusbJQ~YG;O*yfDft2d% zNw=31*l{DHrFyBTzT3Y0B{vk+71+dc4x5}=abpbOP-B|anOd2fZnlV|2uKy(KHzX< zb?^Xl@_C~|Cj@cn5`>YkH^dq>;u))Q_3uFkCT--m4QiTlqZ6fXkJp3T=PP5UfN8b2 z=WU*WVU7GG9fB-d5jRlA!6MwijdtHCF2>3u%`D9Jb-Nt#u1~cbn`JFPD-qGnBKw&D zajmEUkkoml%Kmb}@b1UgPJH zZP^6#{Ltme8#Z8>f?0dhwXa)^MTfc= zr$$lW6hDvt=C`zLdfnFYb99R(`=il%U6?F_qsMxH=vw{XFO6IGSik=X4oLMgXV@(Z5iW z9|FNfbGz}3C@ zRdb8;{(_Tj?Hgd_j!w};)7lKDE#v$WNR=EM-&mk8u%k<*zLl<)KMwI8H^=AA5^8#X z{d2ko?1jv{WxXfH##q-bD*^>I)31dja>C-q0US2Q|H(&)lNzS>;tn7#lbXxWhht0X zCo&g)q&(|Epfs{$GTv6&n0ax>AbV!I9w)u)42!Z&ZEnuSrgbu~r$)pHI^BMQ)B&`B=bPzJVV# zjLXGMUfQ;aWeO__t&-5?900VVh|y1Emd}sAKWRcf&tGW*?cZsF=zpgPwN7r=w53-r zzc6mZk8d>|7b{aIFgNH%NF~R8M2GL?9$3U;iFn`T`s&|PgYF0_N4EsDW{`5j=jEej zuSzKS<62JUpg2ZWcha+2PtR{Wxg$+zYBEr|C;N^zbFXHl&v;(+C2Kj}fVW7*%ZH~O zUm+=0QKw)BP6j=m0XHkb_Qn!21wUONV)z|Aw2IjcyslAz)2&@^HmN{mBvc<$IZ`vm zMgC42Bw{#&CILz$v5&{L?r6E5Ns}v0$Kaq)>zPM(3Q|$XcC}^YobN7ayNB#nc$Z-f zmu=*XS>g1=zlRN@1XaRJkHO^9UnS~T8_C0@O73RCuj`F%05a?1=u`Dd<3*N;`_cq# z4mJh=BL(Sn4;a1V_X@(zuHHrF`-?l?dT_5(%nY}@kxsIBzmU9@c&7h^`&Nfutk@$p zYyt-2kI=|dTf<7x`bT3`vx+mi>?PIA*WHl;0pv7zVOXWNUI7%dG{5z{P16u4x`MR0 zuqK}>=;l~pDLs)fUMbH|#7tbYPIb(RaOVk6pDZznu#9%gohO4Fhh z{>ny3z7(v&={SJOz-y)#9p!+H1hQ@G$3B0qmCYzVs2RbwacZF8_mx?7{^}a27kc|@ z1tKuumqdML8c`-`7(-EkR9LA3#cU)N#5)YIof1N8dV0_#Cpp;IA%;FXEkXLZ+ zaLF8xZK46YjyvjX{0pOSt^_7nbRI*%7;2jK+5gPnhuS!wo;pN4nOzx?4rtbHxoYg16 z4Qcdw@-a*~AyPvZqa?4x1yFYxCoJgo2u2on%7?SfGm_^biRgCp7LlC@ePG>+w?{;h z^VS!a(;vg^-)eY1P)iMA9|o%Ob+#uGZ@8gv2RlqqK~VVfJ$0< zE^H%sw@VPvJfuF)W6v8!ubP$aoE>}l#Ege$Gm8PRlvy_)%%EP35F`Mmn(pIr0^t(z z;mcM+<%Iq|Z49lR`Mo2=USUs`&>TeJSxGoyxVyx|<{3FquI6S$lGpMtjN!@hnAV1V zIZ7^s5~*Y;mrR?;EFFXZ>nI=2vp%LAVaVgsg4 z6%RbVm#4co_tX#8f0LuLiIrvyOthax*Z;y`H)?hqQJr(DQbWL_dAK4ojB<}anN7$m`w>leV^$Z0gi_W8z!vS9X!q&&^|mS+kU|aF`z}RFpb5kB z)ApW*|JW1%4xmVeA~rbtD+p_9^a;StbVDwt$e28gqI2ZvuLFW^GR3AQ+;?z7*i^nweQ{HOg-SZd!x%Hf~x*;`I zGaV;17oAbwa4o)=`Ly!{NJ5|b&d5ts~UJb5&c7kzQEly`CQ5xwz%Ll zqVa8Pr;D2?fYG~MR6Foixx;1LCqK2~ZqmkdeaEvWtdz#$kPnWxF{$L@I-=mopaMmq zOP-E7e{0~PKgJ@QKB5fspe{2;bL0otV<{Ob#yoIo?YrK_#6znQUJ#d}1Po-C#u@yH z?`d2|VH_Rn-D+T|(4fBsm!NZm@vz+5{G?6T+w|*<#g`%1=7QTS6Qjb7F!BlNXu#nX zoysn(BQ5vlymmN2Gd556$@^yd2JTqkiD*}$hm_M8LgQ0ea&32D;pWkI*9wbCQAS^% zKX@u?V$LP(wQH8Nq=|BRCFfQ!d8m8X9Wru8ejA3^tzc%u&qW6?wj+eFb?5{g{|lm@ z+aU7PrrvPjM;)Xp(WDb1EHR5&b8R;!I6JunjtPO#Y6bb8l9l~jovvQ@k>{@$3fU}O zwhxY9G>lE+ilf@MDj!F_Z}4r)W`xkCU;E#;fkCkS!dAG;ueg1#d^=H>UZb$$^s=Ug z>6b9jEKxHC(?bLxX^6E7Nv7LXj^A`tLI)S*Z_JSGy2|o!R}s(Z?C6|2Yb3DPi3)|h>7f57ZvzkPMP;fYRh5R62nd|#%VMo32*u_9S_U~_Fg}ANa zwP8IAT|r{uR28+TmPBNzck@4myc+)qoqVgF}xykb@I~gO+q(fB>C=fC} zId8mflw>>XA+%)R(J@Eu9TiIpMPL(=J$hu|jY8?H%{5a;(!ALR-7|B}?#O>)nl(uO zZx?_Z-G>I+9#eihs5jHUE`SkG&Q}UwA=Sl>JV?O+Z*=|_Msf0txBt8VN{8V&8kWU6 z&e@A<7#RgI7@+-&(K}ZCm1-(7{%3mY_O(z%uzB*dj z5#bdMcK_%3PjYfT7M+WQ@&6zHqjwaUMkj$=o6tbubl%Q|dl@+oH&WIVDJ zI}B@(LD%9#1Yu^yRq)3WS8JC+I{i7>-3`=*ZK2rHZ`uhHgl1TQVoLk#W)>XEzLu7B z<$q#o&m6l_Wt3~;Qw8;!9!ll-dlCKg3K$#0kW7DoEkSPYz)g=H;{Y_AUiu*$pNunO~USY?I- z9dOpJ5ptg`#qGr#rQ*BGav-yjnMwbSvbUgk>tYj{IPs^~_7c+lc{S#V_r&?Vw6dRO zdC;r$CUe$73^#>w!M`vT%73Ro+#i3xntv{|t|sngY8rW#&%OL!w)dK8-$&h_&Xscl zlwHRY8Qnzm`igY!6zAu`roR&(D+t-`(Za`okht-_tOnqS0{|7&Uw}%3ffU2P z0F_DM5#XTnyv@IGae=-BwxYgiS%yR{6(U#KUohAIyCiNicG0XMB8=%I+A`N(sTmRD ze)e&$dMhz2H4_-3>7y@M|B?H6K$BSvtj24;86WfQt{|m1OHF6I$72=yb zYg|C=T=`6v3n}}2cY@gMjBFJPs16*iGV+U!iUk1;dqy9!>!M!jbS`Q`Y{XJ!v%Y-1 z+?*@L^5?Gm?6@1e5KS!W(l0|EuMc)Nl&GcaIl>I$%-;G81Tn?&6(m*H0h=3^uB)Y0 z!l=f+yeES=P%9y{xkK#Iff+)xPnI;J&P4+mcNC+q`VUFmSdv&#AA2FZS7UMOaub7X zmHu@{h2d_XI8slV_I=vnsff7L#&Dubfz*-bW7e76UVE}NMTLpKqa97`p6A*O2F8Wh zxm?cPq;iy^Un4$uKi)a$d(#uETw$C!HJoG|-ps)6v9C3-Nv>ACXd_jBf+G@fCG6UR zljhXxTe0Unb@5*;3cTAb1K*sEWh+^Fr_liTLQ~w>pJW`8o*N6)UmAde?->U@;vLTg z*JL4BH?BSud1$iuQc}(j2eqt<>Wu|zStQW5hn@j;=6wThu|k*5s7IdDfqFIxgwZRX z`G|-0_Rg;EM<`b?Da~)biIbnb*1Dxz@zIOlpGM0UXZyw3ofXiQy+RK z>^1hXX;T!8b);~1)a^dp2q@0OZbT+zU?euJ^-UVi172p8fv?moI*#*WZRG^Go#{yO zg0vDlMzDc#@sDg7`v@uADKVBXCTT+XH(iI~W}Sf=Ej=xB%3nQry4`SUphTbw&P{FP8- z{xhLa>TIZ6zKT_*bLCKmMtE=Bu56?>Q;X3UnZb|WB5~fqGf?AK%d2*p@b?-Yc1`k` zN#@|c&Ev!947#Rr8E4RQBrDUR(Eo|0&_YRZ;h_1J&Am5@OxO}OpvtG!y*RR0LQIX- zaeys(3!695@Rt9bfGm&SKh~oxKdo>~ztLW_m1b&Wy zbPKhhIbZ&7PND=>H6v^kF$+j>30}m{H_Cfbv?Eh-wLl}Bs8k@QJ%JUDc08<6*UhL^ z!U$C#)Q0HDZkl;{Hr?YG{rbzezS#>uKM1K8|s3a|}|==ey#0NxHm};2Ot%;X(Ym<}6jFe)T*k zZFFwO+vP3k&vepcWb&1J*~RNK{RI~wpG72V-wbws;kbHiu#0@)Uw8sAZ8p!?FAmQ= zM_(P^jFY``QNWWap#lnJEBe%jXrJMj8WHU3Rz5w}*m~_O)5Vt68Nx=ZgdGP8u1A@0 zpnU6LA9~|?3+e86fuXzNj^-9&U{3YJM7XadNldjxFbG3y8kuj8*@5GPzJ;z}a@?1SL?7h_l;n zWCc=veZgxHoe0PNL^zrG)jnQrqpYwOsz+j5C#(^aT1j#ta)+c5oJvckD)buf-cKEe z zXNnJ#k*P3Ctmt(}|GHUQ@gKja^Ll4N39PoOaU|U}&Jhz~mng{tB`!5?yO`o~ZhTQ6 zw&g%D|>#sI5d>I+lWEgwl?>sr)DIa7Y6H-1k6a2!syN&Ktp^6tI%?N)Kx)m894m{RX z3zjuh!IPeR5KE$;j!ZIG?p*rrZ>H0yYmiBEtjMdXxGkEtbMoVY22tl>4pmIY3Y#zF zTxFr7%MdVQo=rbMWlAx;&LizlzT|>3zcT1C&Q_c_MWm(F@W}M>s6Jo5s93h|)J5P< z(bFqZPaL>tpmjzz%G!s+f4n;Yv=IWS(oH^_;I!VMIN<9QgRA)y>yPHfRF$x^iOdC) zx@TqiADffLNkirj>jAYcU1!AL(+O@QEL;F=FR^CpD}zkC6ZhOO7QvfQY~rD_Nl`us zRRt9=D-q^fMZaRaGtG=z@npIzxbk4&RM(@F=Sq#H`@)%u?KxXgx@TwTjVDe$rS)1gvAT5>PTg^v>SuD zD=sHpan~%KM=Mw%R_4ig&0%al2j3FD-`9B4hTnnO3UC0RLvUcz&uFd>P?zW*f@(?% z_Bw{2L@PU@8*!+?mU(?Ixxm?4BU^o;13+~^{b>WjopdPK*zUzHr^}w4xryD=f}%RE zVMo5~T>@rVEJZ6$l0no(@NgKKLzrin4a)6Mw5ruP-64cFjP~|9SWe6*eoE6WI(yM> zZF55Gn=7;&2DZ?wq+LU+UI&Zu+ahPyrDAYAR(8@v=F)AEw8=D#Yqa;AV>g1Q&PIJ2 z-3IFh!CEihfycn^K|UU0*N^%ip`|)SJF+~T3;n!{oGw=z5Gz^CeT8COE6yXm#7E_c zL{IUvzR=qXYPOQBpc?0n%0(Lsoo?Lu+#I=$wo348uz0v5ej0 zHiNJ*ia%YPI^}O=6N(SbdD-ux2qHS@tCYnJ>hGQoBADJuIG<7Duz-JkDDr&)-R9uI zWBwtEngmZ;pD>tXF2HUHn$u_UTlZvkf%da~d&`37ePPe(B$U_birzJ}hwzKzhJAJx zxYJQOYRX13AHKr+v+`mhq(B1r8hnwzA3zRd9Y!X}2I-vA5uRKX#fsEJcf9-EQ7B!- zh^n+`!1sjwPu~;$zk2K8|D3z`>HKeDh&h&g!X$&${vdoLt`wyy0Zi6@G`eRg`v=5g z4dTSd4ALZG2RywBYRPqscsa;(9Ih+li2A4SsoP&^LUJXXL&5i-zNZJLzpF#?5hc#o zRD+uL5$3&ke)1MD{K2{iS4#hxBjn!a#`F&>L(~bBZJHk?n%^Xnbp8JF`;*Rs@fl6` z{TEJJ5O3%B7SQG!l%bnzekgYsrEWP>ETM4SdpT-c7&_* zII#5iv~IdUlZS4Vq!1fV$p={tOE&Zmmdlce4OkMB~mT>Q_3V4U_&SN-(jBT zV@&p};N4@OI%IpNrW>TyzmR% z<*{w~Ye1jgYj9ACFZ*Bg!2j!Ys(HAa9inyG+Z?#DZLfIC>J}@MLoIUG z#hw5f+?dqVFE{CKq?r{39_ z^V%MrA_WBNWVElFa78vFRtX+bWW3j0#|>T`f0Be`G+rgWeZus1xubGivcl}{H)PD4 zvYEDwTh8m09#`ybjbsgLD7xyG*IgX9yVPhB=}&hS)kDdS`{?w$UO;q-y*jt@qBF>N zSd)80Vdw z?v&&*{Sbp#vSfx_x8LHyZ|Y5cRJ0#BFUw1dn}VQM61u&JP?j|)oMgtkS5Ge9S{QAh zeh2S`>XH?3h(8X)PplTEY5Ea&8?a+c$#I_=2<<$v+toqeoMfrk5WAJA>cxq6;PP2B z57Ozf^xp08U)(YOj9ma0>z1|4aX(fMl%dT2z`S0jz{%VxRQjA&j1@ng`7I@ z_hhGPw$b|WDO3wfarI})-?Gon&kDLEg(dmaj1(|?guBI_+|-GI#u&vtByMLBblONe zp1FeFQry3=+YY*nd{*l&p@da~2ke2x&!HdE^JT14+)^-~Xk z&43#~`m5~oxjEY6i9^K~12_r1oBnKn(*lz*Cm}7eK`-1niUE|E_p$g<9H#r#Z|{dI z*TA!>J3S-tMMI-b8F)jN=#LRsws;zN^nx@=D6$%m>t`p>Rj-A&39ZmTf!^VxOM{ci zeC!w71>mZIx-ONxA5J7C&s-5GU>KoaBYXEi<+Bk%tb<|g-NXYf!nKk#6wD^mMv32Y zkl9?m_rAG#@8-1Y4ZNsP2E|F_X(fE1N;<>-$mQ@H3DkYPWwr!CwT14rT-slMKjY?S zC8YG-16rwz?3;uDSAKi64|P=Kq3a-RD$`2*aofkCEvfN9iwRLy+H2fB4t(u_yIHA$ z!^5=)ed$KHXGaAYeJX(=XwO=O68rCIbvu-q>t;82r-&2^HKS;+a-yU=qepAaaqD^$ zNA+7&(Knc*)S$Q`i~fFKY~yS|W;SYjReZf=%00#ON~h|lsRwR=jRF&kGRPZI;WoOCB@(gCd&&;@b){O8TPRvoDynJI=IhB=}y=1o1^TP8UDr?sL zNH!#47^27mrEfUx@8@~Sx|p!Or8L@Z<{IZ7X9wYt*2Mt|FuIN}1GWT0Iy23x`vxam zRq2`HsN{5fe#j=gQ0WzjZ6&m5%5X!$pAo6E2iy$^JOOhhey8i9{JnUu^Vj125Ag$Q zviQCMUp%ayY!r}~eV^Us9RVU2WoxnG!IJXVrmzvB;pS$r=ZB5ipuTgr=JFESb{a=+ zD>$_kwcf|QK~(w=3vep0Ob;2!OFin-gEP<19bHd5#_+S`o1HSuZ3XB3#Z@%wj&FV} zyGGNPhxRG264nean4-E)ruejyAiRm@4PUysWNI`_>txe_oqRxV4G#IhRoz2rcU}7G z!I}RJ+mEsvPP>hBY%-kjwU!IVgC{#9S-`iUJ8dx*(NQ11LXl1tq zyAxIE7ml)VyPLL9C#x7kxal0GXh)hpHM7z~C~NHcKIh2=O5^ttLR!Q;C$w{O3<+#j zShWofS|rCf{e1?0@c7pm7+@AO+rsukg9zg&SRA4GwJWrGq_DMTt~2HqC{urxOl3Q6 zN-HrJI057H(Mi6Oz8{L8Y*~H;4#3dg2jC~dGm-UMZp~!Jg%A6^cdlFQ@6twpAY)5P z`zf>&WXk_~iLzNyH(6q;uOvEG|MDKh-s?{G-X{i0JyM<}BFe`dxny6%l^vmN8JOaO zf6hR@2==1y$CoWW(eIvdvk~rsywI1o-kHvULcULTJp6@`{oF?sD;%8v9&|3|5f#`x zwB$eU;~!urOy$MFGb|OiakzfF%IKY#W$DO+6Y!QCB`-)mBiA;deg4tLXc_4m6#ra} zb!7V>7Z>B_+E#no0Z>;aH+FzrSKH?e)aze#Z!WENvWZ=%V{Zi7Of+4CwBC%VQuuzM ztlO7zj0nI3g9l{>&6^O8TE2~^OZ=*N_UN4PV-Mn${=WwQCVvh7>r^j=TR8unY3Kw_ z@7^>Yx0Q`ZL+ZKQ_FzW?9E$|jc-QLNkea>53&RSE{vDK%viyAH*ufIeaUst(H({^u zEA4c?cYSp>(Y_|VBoqny-Yd?yv8X^D;~RWbPOY zPYaB%q7%AeAovzs>iX1(tc|WqR=_Av z%=HBPRJ0srDfze5hICZik+s928yVGB@EtncmRNs4P^HUZlnt8c!;3%pRJ)dbF@cQB zWzgdD(|#h1zo52<)R=>G_o+KZ@_FrDcd95FxFtbx8rxd@M9}6JdXa^>SxMs3ql}?~0<{R@XsZveE!f z_mE6QT%8OBo`|Oi)cW}j;$l-D*&qKgRM8LM z6RZ5iHW;KQ&-W}gM3bNv|)~k z;*s~a*xA=c{g#`tQim^Ut|omhZ|bDE<;pDRDKvJykearc#~jpep`vOSr>MVJhk?IY z2LWGP1^xOjVnHndLf`9~G8$)3Fj_>}E`7eZ$1FH6cp8{@z zO)ncM#O;W`lm>U?VH02~%PK9jW*Z)rV8EZ@&Jp$avHaEl^4m|fVE{G12zoteGuDRiYL|^P+RyVB8X3bJ^_B3@jzbm^_Ikb{L%AzIj~uJS<_`%UN#H zWtS&xnFIgLC+EQ-VCbHl>w-+ea|x}EIc_V;M)_ZnuTp2S5U^Efg?>*FY>lRkqx8j- z3MCiI201Xs{Va|9;1Rodg z@-UFUmda$hDK{N&FHBA_X~sB zC7V#sX%9CyWPE<$RnI&10W+Zi*3ImritPYKQ-i0pBZDJ)$QUDP(cvr4B-goJlMX%$ z98w`}SUaz;-iZ16_|GmT+y!M?z-dDHE_v%0hPVku(+q_QU^ifMv%>H9iurDT=BZ1p zzu(N*XPjZ;Mn#P@QljIo1UXzV$K6-19OS)$^9MzN^;g2lAR(oiftI7Jqb)uZG0p6q==3}z}nq%dN88$ zC9oWxX%dmhRWu>>-iLL#lzU{pxR)dZZt6R6ROhdr!=2w*7*{y_rbf7Wg_i7q%l-=Y z{Q?)_!KcTg~Xl3E8fS->H!SO#ntAX27=VBXwFo!xianu^+G28L$_DQZW&Y&@~E+anP0H9 zwcFeCHJK&s`wZ?LP%}dJDm3;rCrSlNIe)2QK}+HVR$T^IT(JD{i*E4X1x>dsa5INF{V<;lMb!kKN!h`mCm+v^=0yXJug5Z6Rzhc_ zF`+Etg?omZXJQFWJ$D#{ug-xVu;e)cpPQ0-^{qrP6;+=dyUVd`(MMWqW0NGb#i!;N zy-m;#zS}I~U73mZA%Wz+Lt_6}C;Ev`=hR3~!AMP4iiHz`CC>EuJxan7Q<2FVPi^3t zb!7RQdmo`?L>1dAM3HmkSzx8{j8ARYkMEP=sc{ThsR9N9U1To?DX{uquz&j4>7+I> z_MQ9KxH)A2M^MDq_@#gJaD^}>FbtupNNT1KQN1%3OyuvH#HsORml(L^i=*yGnxTtp ziYq%>MpWTXe~eK?KVIM;TNPx@K1rQ}pHFag>)J(>nOqJKpRZT$=->g567pYO5zMjsqf}TATQ}&(Xgy z6d9Etbf6su7Rkp>jCll_bAp?n`#)mQe0+FtWeW-fN+C994Pq}E2Y)BkTM}j)%{AsT zj!0*XXgbwr9|P$3Z*ciJwNFGl)YRZ1{MDx`%h%h!c%`@t&do@}AV~GGv%&FB63D3S z{A@-6p0Ux(r(5i_mBj))h-02d?W2mM0^`I0NyE<00KI70V|!N#yZt)w#X*HQwya}g zuJLu_a-xL};!byK>5woPJ$(&yD>AR8=3-Z8^E`})tM5sKo}xMxCO9bVu>j7Di*WVJ zB>7kILo(0IaMtUts7zV9tQxaKx3N$>y=CLHVjb^SV;0{sbDW#Ft8zHMMm(PQ0rvb^ zxgz39I!MU#Amt42$rEB;))V#4OhvpdHs0u(5lU0hwnRFmHNg7skVVNS0Bpj>^d{5G zYS)c-GdkH110F}MYhyR73_mEXbga(NDJQU7Q>QqlG`$pLaL7!rc5~mEwh;lK>+#%j zpl6Z~IiwP+N{yt#H%g)tKFY`YvL10RBCk=B&ODCh7=sY1GrV^V*TB-I+2V`k`9Y3x zwgZ)SSV!^)Ll;Z!CX$o2wc9ZGfXB3|G8EzTeUQqX)sv)a`lchO{`1+w=cdqTpYbQ} z3LkWu?Uel=TkqjWb;JMvYe*tW5?N)BgN!&QtFp2;+1awsk?l~3;&5!)mF$(hDRPcO z99tYC9h_{(c8q@Sx~}WKulxJ?{ReSQ&ig!H&*$TTF;6i5odPee?EIYqd;Cd(1J3H3 z-r7?NLUuKcJ}hic7BIcv>{?ro4OJUqdWdB(*(o^28@a-H1DC~RGY!cLxF%o!sHpcq z#ymTs?FeM83Sr`3{OS*Wfew(K9glI3&=3{1DGnY$SD`}37q|a#a(C1le^p)41HEp<~;z5sl$Rpa8zEIr^ z(2I(tAi-CQ9}|C(o#v3lecBTFY`FR3P`PXPWDGm?X$2Z6d9R7uGOi)#szgMIA}YDYZxvcGdctg@F4#phz#~tHK8G%6f@GS5_3D`XIh6MTh;jKY8Cw#$oY!jQ} zZ`AG^9Q=ndL-h}1W}hyTl=n|SoccQ;M!Q*X6zRHdHGcS40X*`f->?EVSMf?jv$F{l z=i!L|xcnuSi%9$m)HiMxUIidCw)$v(S{*C;X>TqBf1a!gMkdPg-ib_6DwjL24DkC9vVoPVh*zYc-9)18Q=ihN>q(n)yjN|C=;Z z_J=eh7qtBNO^j+K=x7N%-y0UzJ0Wpy{)OT?hIMBEc;fNYp|5Z>w)r&WR#;|+>-`%w z!*t#*rs}&~6UC#F|EC3hBhQ4d!r^5_h1>o9J5K~5n)BbOLhf%DOt|GW_SMJ!4u=g8 z4dmo9(VtRr^>BqrKAdx$7SIX*YG)7a(v(f+JK6`X7SvPkWI`=C1}6^>*UFI$ST@|% z!6*rt{`2zBQo;51_`-LHW{=1(L{RWE)X`^uJS2UTILGIc29{cYF%-(q+{ z&INdGEYqDpSXAlEla@{S3Wr(^=uskXw(A48Gp5dAI~zr}i&wqw*!;Y-oL+4zFb1sP zud&P_>9_RMy1U#CuWW{d0I(ku8CArS+GlScTfG3PVQ-dRNAsxC>J@M0$+YfEAbLG`85SEugK=bs*8IILUAQ>WleOdJRqnJmS>&t4o z`G>1sZ|TLVMZXk^)*Y2#aJ$2J(Q1||hv?6ONsZ-J6zp*AA`JE8=dsei$ewo9Y_(Z{ z6422yw@}@$RT##%B*-62iFKxe)!%d4X(_Z{-d{5caz2^Vrnd{bdJbcJ>N}u|a@aS* zh|NhGUgTZLYv>h@5KFvTP7!&uq{yJgdI`^`^%wNvW>5Fd+fDvXRN3>=SMo`;)w4%W ziFOEE8CbkJW@@qamTPWIwC|(AJ|I7_(9(~1_}d#AGQ1Sv)S-usy`rw4<_F~Gy)G0y z+5xh34;V*fT27vQw6(YE&l41mcUmJ5bT96FLJ2;@rf+`=g73Ine1IOMIkZ@?4#yt< zOj{GR6a6Baymzjoii3;0yFFN>xkOpc;>t9WhK-h}-76`#vAj?w@i7p1LZEn)eBUa@ zJir~#Ltuj_?emTdKRn;0bu5ME-5*;=Z`M?bzU-6^3o;nckN(aUcsR<< z*P*Jpr_Xi#WD?SVm2~^?@d9%K#SIAStMQrH#h(aal^JsfNR`QbMP&N5SPJ!!>giRO z5|;YS*6p=^@s$V@mh+aM*n~E^oZ2-eu^=%w)}$>$H;p)5;!QG@=Rej8TCMJuPU^Zf zsR+v?9k<1 zp85I2oBUNm{+Aj&QVOr)Ru9>`rG7P$vf~T+HSEdwGBKZ)dTNPCcXlb*aWmIT<3y58Zb^6(ZpvvR|Rkd`fVL# zDgs?xlHNN!*?oSEVwN8xvC$`$im#JW16@OwPyOe(Qc(6CjF)Ib~ z070zbMq0ny2r`~ILWeY=Glsq9`d)gy{Gwhop*rt(;vLWOb|J~3iU>dQ9c-SSkNKc6 z7=`Advs6>B7$BN19mOS?S!8Nc>bp`NLMZA4lmX^^R5O)GC#vPps&e@~R(Geh z+&X*sgvn&$$Lfz3Un0<1==G|}eAmzkManvIv|F*Z#WtBRZm)l2x|MrNNlA_n(wLj~ zTVXN)gbLg!gFGuFuY5>ud8tfer?yj%AE!{6_{+IS4dy`3SskiWHT`d_?(40*F4-nq zZdHL0nopiYEE{`KDd4p%SU-VW?!Sk$aa@D|IbG9u5i*`S>s|;2s8UTn=}bDDSGC&{ zZlQmnU0{<)`k87!W)AC$fhx7vrAsrFiBnXfYN3g~O4lCs^&xQ?L93FT^H$4yx>SS8ZT+7*$*M5x3?Et~*%a%14Bz-*_2#9edO7H@ioJm|A5*{PA)f^M!2!Zb{AKr zMs(#rNPL#o_pn+5vOdeJIdf}j9cIVX0})g8%Uf_eSSxpN!`&S+2-{gD_RLVP4f&{vFVw$Ejd&85U!x!hX1o$dO@hkgx`vM$X1BKREvUm)+I9R!(Np+V` z?!IfBCQL0LOyZ*iuRSNV%a=D*s)maP%nrliJdqepl@=>o$G?zvx}>NBA%1Tcel05r4Uo$3j`%@Q;q&qo4AEr?X&|xl8`=+r7~| z=Kk;4><2O_yRvVxjhAPONV9XQYw)CHCGEYzxW||8lYp-_|9sX2kb+W6Y^zPsGjU4Y z=(K@)kHLx&Bbr>&{AFuww5uK>66-(lf2_Kpu|SCD_tIxw#(Hy4RrYzqnFj zT=X5tVVd*e5m%ZJ%((W4q?qd9CQ}9U#72D{V`s8`v^e!`(u%$e`DA^soIvAI&$HzEGg3s3;eDcy z!{yec>nARTgs;|L-WHE2WLN@@Z3?L;AOpmDWh^ZvPA|371f1{dZJQ=Y9?aR$_f<7oP6=9^rmxjK_Q`N%1OOnh zG#c@g3m#nWVRUW!KP7^0q9)cQaV;iy8ghPcyWUFu_KU0wOx$^tl}VpOgp(1(SMaPT6panS_`W z-b;cPL3WvSR`gKxT5<03)k*MF;DkG8%nf!8lbuvJAIk}IdjZ_lrrXXkGZDI&-bka& zXLzP#Gx*e-zqVRdJZY8`jCZ3mLx^))CElKp(f>}UE`|KSHcq{{1>?&XP46d@$UH+)dqIEhA}^A;I#F^0P_{3})88{ATHSjYKhwn`Ne!;D!CW{aoS*ob<$8#`qv6KA zs`Ic1-59|Sy1(&O(Yw?6v$iYJnbJ-QECIK{D5{4sc`YznRAk?1glD!&&C8%)WV2gi zn8~VK>lX?Bj6J}|Y0&=R+WzqODaNU|c&_U@KN2y-5||cKgyQmjDuZ?g|BczU2vLV@ zmd1MkX498%!wPp_U0BR3910j_;`9^Sk@;n}SFxPwAG+_^$;fVy0A}LdD=BQc%x%D9 zE5Nwol;-fv1jvidzsZYlFL*8B>~b_kl@)C*sr&+y{7!1EGZ=n6&VQzLmIX#Jj?cL) zuW1!BNftfGZ-3>CXIpqaNLxC$%>7K#+{mXnJvT zBM2yPz_t8E7x}9mbZrh3x13uJ7Jqw6ipX;$PImtQuKEDL)g_g7-hE;rnP7_9hrgB% zScRjXrP7(3->P7A&>=PRBKy=I75#a2{cCyIT6}6^{yY{$YY*O?QCweJuP}HXk)ZX6 z=B^U`O<+1w0UXFiN5x{)hBbH@79qwgKOVgj`U`O7{14y?2H!bH;g0DVLERr#Gdv~8 z0!kzyT&&5PKKmdmudw=+66T`gbS@KYzHY%sZs-53AcBI+-wBQU?J2ol@t3FMzgHjy zrM6c#zsU4&n~PW<9F7ik<9ijJcws$aBwW}So~nNfi7)0J<(~IQW34#@&2*&GeKa^B z?Ug43*F~6VCq`8OGl*)yO2Rp^N$V2Gz705u-tWk#-|pKVIjh@|M%y9djj#3WeK8pHc}_v9Hl7+P9x9w|sr7gm_EXK3={U+}gI z$L)Msw%naMky#GqQ6=;FUd}ucdZ48r`iSSH1#B4#yjaUN-6+*Q$y!`n35J(V#K|yl z9G$cFF82+rvO}FmA@t5Vd^~Kog799mNev(@L^a-1PHjcvQX3ft44D0ZH)cd+Uo(Oc zF9xxbTKimcQ2!BbY`A0p_2bk+%GJ3D-scr!<-uL-!IM>t3!xYIQE~%GpWYFgt%$Q0 zeg`q@T1Z|gIm)rSAqkOge0C`&-(zJ=E+t8>^ukB;{7U$b4tZ(#@GsYvuQJ~v2T=af z!K4Cc8*YtT7vp0Vd+Qh3%HzgXW4T3L(J{t(f6nQ-xezT>GsiomDw}Iws(7Qgm3RB( z+F$p%N*X?Z4Bomz&U#kNN<<9fs zZepqp^Dv2+sOPnhoc?VxU?NT#1aAZTDFb6*q*S@;+|!>s&@F53I z6jENk{uyt8bz8p#vMWY%?jFkbOH9Ca>=Vf^dY)Kq$7n&|-C}8amvqZ0DW>d1pZs)9 z>ec_eKc-kP&M&@ZWQA(dXTx#HkKpi_^T&O`?sBaBu5c+XIQm<-jhXjN>1{ZGGP{_Y znZ(&R*(3gH{?`YenRq;WmRE3{=>aJV@MC*9z8M{RDLdbI-%!o;*anv#-SY-1v+^Tr6Y( z=xpT7XDqBn0>Hg^+tXz&O*0Ph3*@yJ!hK0;3XI4hHjqA=jQAq%txe zbIFmfEAz>eBL9LA+wSCUJ(DfwEH{hpwditGM7a55*(Rn(*H?{E+3d?2n=R*v+ZAW_ z9Y>G2W*_dFEcj60jzcHrzl@pY#D8Z8WYLVScZ9G?Vxy|?l>GKt#!^g!e@Sq2T34eSB_dp#qj?|@2~bs z)^K=Zo&5e(S%IGs$MUbvw0HXwDv1Oml5ask#CExvTbt?+%%#kub5kBz7F~~fOFr@u zcUB+G&nnJ73s>Vi_YoNqeQG+}b?M&64>qv-#o$w`{PJX>GhVkTr{cx4`Ltx4_Xc{6>mtG2z(;#R_^%c1eb7_K%cEzms?qPM(d*cL@<}_mne`WP=;(I!{ZqsJ~6D$7T{N2g?=qZ=u zI_F=&cS1?yXF$bsKiJB6%7X*nNkMJ;ihU!{AAqpH{)Vu;{0(7IT}~7`u%9QON4MYO z&0Npt{tLoVKfUu!5kdF^eU}6wSu(xef~E;w;d`ltXqZAOS3?txX*=6pA=JaxS8ClQ zykW&2d#9$fx7W2mcHy})YV^ZB$iTU^?ZRFO=se;H9l|~O$4YZirOhWbFsA$B$JcRY zN%I!CdEqF$`pk;x`~HU*;jqS|y^$x{y!U0{YCD%%aN7L{WC5DRM!l$8iVro)PVdQX zj5kOcf$y1u30#w==OaKFrMXXVZW4Yc7T4>)^@4IQ?K8I6h8w9)&Qf4yT+Cd4R*9Ygo>&!X@b)t%d*?M7?6B`FtXXX_^Y^bR#!GJm+3on+78McAgl z)z4s(JOlW9SbfS2qmAs`M2MBV5%5yWS@ct10YdldKmH403A8_p`vYNlhR|EO+2fUE zHvYLb2O90fw)hx4wCvnokYz*IZNyzJHMb3FIYq|+1b}pn0v`Rq$tKpM)I6t6HdY{+ zzp>Z{QB6^WaAnSB88cNq`bLRH#U-NAV$#RXZVqPv8sfh(j2mxP)ne(h`lB-Gs4Q=!fU(z2C!W19g z`OKg}PT%&6%H013mctG|T+ z-4=}pQ+6wvxn!1&928Nc3RGsS7~@|MmW1qcKd|?zzz?B~B6Xm$CM)(pVxb$iYmI*5 zBTn0o=!Q(&91s*Ab{G)lpm))V#Y=mwY*pITk0ZX52@f_95=0NLnSPG7e(xx;JyYXD zt2<>AeHQvAl5gVVV9bSMDGPblEVhsCPM2O+!jGB+7>M*MKRHx&O04rJpGf1uo0$O~J{tJ7ubudAyhr z(pi}rkj;kgn1Dm&QNa~_wzqEdglK7`pyPeh%Pkd-v6HJ}2HGAH8gFLP+mFVKtdzR4 zl{HINa~&XCB62eDBe;aP=Yi-P0QX=Vn7_U>7qowH62qs(NPN~Mi+V&MV|Hp*v|iWT z5?BR+9;xN+2AdxJB16OVF9KI|?xFWlmnnUx0VtVdN##44L^QG(f3fV=KB0NrxS+!( z3Z#*QdG$(W-%7@hpx?M~=h}~RiXlWkpvq<;v8~L6e*i{()@$|;!QUt67AM5ta(yU6 z5sXH&c|%I=nY5p41sdhh+h$C!T+moYCw#6bazW*0C7h2?H#9#4$kPnnQDUes`sqL~ z%`uBs&pIJ^QpveL$hrGGLBffQ5ZRx>;MbuaTH-UF2s{HtvoKkU*72i}+c`G}y%a!< zJ-p;e6WEN+6~X`72#oMad)rBARJZ=1s%xBRQ>m*Qmbl6a>}l9EEs(ckp<$K5Y7|xZIU4BgbjR7T^W;K+-PDKnDL~Zg#&!> zYWmcBj$V3mYc`uC$^7+5CNZ0OUD86~##imHXERgb8?R;d$6V?%Z}VFxJ^p%u?t|7T z@NnDzY~~kPU3b7ojH4tir@cuZa$~l!MD0pCl0)(=ID z-F!{|kCqjmk)W#Z|(UzxE-DR)WlmHXhEaSdYX92yLu z@4Kd+DL#bNo0}{U8UD$do}Npl33uu(k*Vho(QB2im>As?a78xz=(y;zCngnu+S#|f zSz+AgBFnzye-vU7AYcbo+E=w@UShV23aS0cx(}S2wRh=MY3J(IighYR<^$ooE7l}j zl7V>HaT5CVLm#AUU*ggQy(=MnMt_g~&(ukJ74N8i>=pb=?>%V}PY z=6R$oJ^CcZ5NJM?QPRk2LqoYZC7wr9!jIV5^Xvbi0pXL+FxCBKjs4o6_8?R3T6k4E zHVc5Ln<3sofbzxu>dzs{!HKm@vCc%dH<99cV(K7%_8j6U(4b7W&J~X*a|+Dl^#JC? z^L1^xIZs)_-F^duqAdYnklE&LObx;{zwQU9oYYh|xx@h?|E3Rw@VIV%E!CjLNu5W= z6KVd|HZ(q|XZg$}5fU)~I~9P^h~>BCr(a2p1*Sh0q%qg(-dk0MNy(Wx729~7+k7ax zEY43t?ExbLRsOzAVh#ulgbgsR*HYj4MW$ZDWk#>Kio+i*>#T%6d)57vIT(#9i5d{A zE}L^&-w*ovz-Lr;O5=iFOwlt|?BSpy%l4notGp}n%{v1P0jl^|F^!bc#Z^DrXqlgg z141+u$wGKW-K{mWNZ-a5c*f#zFha)NkP$s4ItlGzu&|<-zHG?fXw5Vq=XrZa``a9` zjMBEp#;4h$64VIE6qQAuZSMmjE?>F$7IZWVi*EXPfs0|Ev7dL>igce%KV@o(i&m|5 zFsPDSHMh@%&ffBj3+M~8?DbWzt1)hPGWKF0E2&vaBdb!|j!9J6FS7S(x%+haXR3Gn z!YD*G4|#r(eOx?UnGHkV8wiINHi$imxKf-XEe{N!3P9f~I~=~NRJ|-A!Gk})^=&H) zdm78N&!c9BrleE6kc#$d-3O0HrY80*_I$t{3rBUbSP#u&^0>P=S@c0%CKtwPlUsMq znL$jn{^*pX&P$fNYMWkY>{_Ypc*%NoH3pzO%tPNiD!Nq==xXUy7tZ`+H`@*!ov(cq zf#*q0I5@XpHdo^j3l|m{%qU3vMRp-S(7(DjB(_VUDTA<&0EEUItdVT4y~Eco=Xx!|u4TI*$*&VE zPub*TL<@^4CDnNr!OQ9_&e4;4rc_jTq>y|tsrYf8I_TQ#tFPV4{F~(Cpe1uimztM$ zzsS6wMfYq6Il0c{CfibTC^cCclAO1)ij@-HvJ{Z=>Y_U=px=iL^jWO0+-OgY+#7jq zV4j%Vqdwb=-5%kLk0&e4tDm+aj@iHFk@4q{)QR#BzDo!D$!U1qY`slQB6rZPO3HX* z;RqO3ZN_A2qBx$LSZS9(w*^o@+tfJ5%y+Y?Xg4*{xECrqWuB)%!NU5Ltun3#ayWEv z+n!Np+kQ{3VsGFk04O@(LtU+#7YR#6mgg|DoE9OeVTKmDSoXO0d>JPEP18fXFf zdt`xiseo0r;-N{v3`CJD{Nbey`|mSF6YZs;uLte6RBO5N5GH;_akCyj?S@E-ExL6YJK}9s?KbfS`-oUOCW2t_5}Kkr?6w;$nN2i`CPx3%zcM7senT zKP9r*8P>J66c^8wMMDSxbPEL$W?*<*B2vGRk6`i7yPIxpm}yhORjV{d13ny)oV9sX z8bIAP8R*rL7^kKYnozM~Ty$UbO=kFwsm3cxcGI4I5+A-L8xOsRxMN}&llzI)$gNL1 z(ZwCRha~{skB{ zBhdZ##T}QaFwAw#M_)&n)fUvRuBe189mUkBI(CZP)#--uMg;+%pc&LmFza5<+uXXL zdN1a-%dTlesDnfofQ<(t)IvsN32!8N%7powUPZ$0`TX$*;QBbs zC`;sgnEie+aremSmCb8WL@6Nok%pGTVZG0?V2Q)x9}a(pz^_MoWivBqJ=D6Vs-6t0 zIyMCUizNrE`Wu*PPYq*lV+0u*D6r$&kvC=4p%4#(PFG{BAsU>pMBcVH>w!a+>3*_B zML&zyb`P;ot(y5C;8V;$z^AzX0Y2TSDREl|P96UyJ{dGp-Z}m}`+~4SQ25^yM^EbS z69;mk=)9N$R|@pFV>GMtM|)kB7w~%T{qS3idT=gQ@NjRttP1+(kJ7`<-X4vV#CtP^ zadOkKDSXIX_QII~%|esT{)*a z47m-iHYopt&#ZPyaL`CV-y`EES$XxhatjU1+=wyERqto<%ZV!(RS@p6p4HJNV)~}l0F$EpLyFN;6d$#o1?-KTY{2_>x;bEq#cQWN zgI(%x^T197AGTD`X2es)y)tt8jp?nEn!`IP_Kj02H1^P{??)dI@cH_0#wmiYETFb4}XRB7&fpO7j zVT%t{%KBB$l4|ye+gM=xD`FNVF;#l+Ea#Npdh6+a2poo`&K;aZCQ?4#W4xakn=Kd( zsXu+DxUl;DCw?lhz-xgN^t4j@f=2F5u1oQL{v`+MfjQ;5IaA9iS{V$lSzhcOjPdIJ znQu6O&s8mF9piHZW37c>&=q>LAL5Nq&B5k_@=`~(+P(x66BtB{Z(!)JH)!viD3g0}F-56I8H`#c`F)fYbg^oSj4*NlCtu*0 z^$rZ_uZv|rRgpN98?@>+-*<o>o+|*t=H4>Wc35 zM;1+Dc)?yqeeZ1|Y3b>bV!#Z#udYW`mq`f8z)qX9on;7N+#G+3eXfO0wKxZM7cKpw zkQiPvFwx5CmM%&6A#eGuT>7S^WRqMz=ukl01z`bb0y`qCW{lY?`yr%!UAZfFhh$WrD<;8+%zaAmE1VBE@o#cq;mu6 z?s!2L#U~{6+DakIiqFOo)IfZL8W5_OHH27})403@@GBnGH{_qMjTbKVG6>jw$ytr0 zI9ZVwc0CxD9Mgc|8Bj@EaP9m8`91{37nqH`?^c_Wwt6=Dqa|Q4v(;iX2vQeZx!mmN z8%e{h=XJ@dXFpiD!F%l4eCIiahr%4MJ_?e2@OX3dTWiT*WKnz7FGi8KCF24+jCd1K z&9bw(Px|UoG?*-sY5vYlXZ5E2e+_}I@3|KhgX`PIQHSWUawYJ8!Zgpy4)@P)n1^R{MsOl2Wu1L)#M z7hZJv^E&9v^Q)s&^PdJko~!dl`?rhq9(e&w{k68C2Whqfg?RqN-80{VO~N zJ~=?4grjcw$8p8Z(NeuXaZWaGS#&CH0V5*8QX4GeY_g>&`>IaKM21yAq<(AUMcp^3 z@(>pwfVH1|*npH>JtP7G*uF1Y_J0JhAFjlF{qVMps{LNY8IJSw=O5qi7V7b0Slhax z1pVOfuY`W6D_mP*+n)OlxAfe9=`4@&hmmi!_=JHjKA#N$NSFKvkX|a61DTKIS4x+8 zDB8$-tH73!eK#{dw?u>*mOV6%$=SF;Z6ZWgm?ZS}_Vk94WsM~B<9^4Zyf~c>MLVha zRe9sOn!5H#jF#0G$31f-;|~Ua=jd&00SL*`M>`{!&8<2^E+fy=e7(dzd+K56u4j>g zM6ivf9l*tL#=bXb_@(@2@&fCH2}?&S#vT#UemsBpEaU(1S=`P|VJL1|sMhanmztl} zOIz1QBRo=bz7cgic_GP%#$_`of3jgpuEC!aC4jynH|C_iU927@G<2%Gs z#9;w{6^559F=M+JFR99W27E9&B#^)>`=vmxNKHQ(CiII;R)M?c+%K}|Bj;;>$M^3Z zbv!k6M3t&C+I?^as`o~DsjPK-!O}O88cjciz(F_vfv68$`KNoIQzz8Bp>3-NWcYtL z>I#1{e5dm|+2;X9i}?RCT4)Mx`~^~PI`d9i-7lnP#DNn)v}`#`Pz49Iv6{I*BG><> zA9-Je`D&3QcMhEQZ_jHp+vww@Q)?wT$oIi3h&xJ;FY$*AUOqSe(0l<9)Y|87UEHp4c*^CeclxrV^ zgku;HP)df9&qS4tl4ad!CN*5+ZS&*ch++i(+SO=!Klh;po}6zaCnP4`X37CJhz`b% z^;yTairALKFg6F{ZJ5-zS&#DbAUl0F1D+?r>4}GTD!jM+MsJC*$z)p{j>%@Va{QdH zv@-~67(;ZnVWSi=N1hf!-`8o znAB~i0bIvjK$mDYRrS1|^XkIG&f~+eb8>0nor$eZWpzh9mo`#ap>730Y$mWTnsNhZ z;TPIuHToPfSX#?Dx5?xb-1K=|tk<*iXp5*leB7$fx_-G|m;EWt)|^$ry(Tb8rI^Ru zw`VIaM1Em^tf-*o$*|U&3HaAHVO%Qgry4I@)(E=;OefbE9*KRMhpK0Un#|%GNzk3a z&(x|Qe_=tz>-aTz&2}G?17%l7&fz2VqmY>ozzli8%T3D6zms8w{#7RBDUOKUCqYXj z4-gs-DN$dXLyvMy?>0wz$C~W>!BE$_UR-c-{bEqiWNiDe*SdCUt#Z8%BHJ*3FPAb? z9~Vo&ve#9mCt9X>&4;J&RA)|~Y3bhu#hDq_xVJ%u7nV^dy+F?0K_;R)4MA?!>#Ufa z#Nn0m!8Jc!zl^-{79iiuTzQoLzM|^2L;^fTHTHojjJ_=x06j2j`}#B5BOY_Ds?Uca zhZ9ky2;skpH+glvEKCLQFLUk66Ooi9LSmnpTBU@AKGcJ^C=pzV8!DX(N2jEMse~Re zK#k~gqRG_QJ6@e+D~cEryAY@2w-$6FZ#j8YHbrgsm0ni!pD&qNf%H*N-Qxc3DwbSy z4t9~|_VGU)A!kY8#b{slGOhYU@YnMq7N_#o_ky*wBy1>f*hAE-QxTd)y$GjpEp|@Q z!6+REqOy{Hu~~kCpfK{rE~hTuv^pJs|0(lRyouWaw9=YmJysrVs329>L#?5XxW}xt z+|;Ju&iY%?f|b*334rzmQW}MfFO?A2MIB{J<6ch?5`K|c0uQ77ZQ0$yK>zZIAIVWj9~un0!4m^Xl-zi`bMmLuH%>GT%6{{q7bmM3egN50P6!lSp~2 zCQ?c+tdN~WJ zag=1^+{d$2Sh$xB$(R8mv%n0g=EoSAG}77h2!b<58g+qD=ILaa=tH|oYF^1Emv-F! zxm`*@B&SeQmeHlNkBy~*Qmj165fP7iWW{c=7<~UWyEE73y{^*5l{+NkkI%F!tni9t zQ!RIu8+c{+!q;BPqWQ4;0V$SgZl~-Em$HKzWMU1ACM zPtn;Yp6=q!{%Q{&N(@Q*uAAVq{HJDAZk+YtNZ`@G1s;1Kr~*ujzF_dY5=lv+_#QV*84D!xp}$ zSchP>mX1Bl!nm3tE7k5SsJ(>>rhD49-EZ=1~s`juR2Kfxo^!ST)p>JgTZZ8jY zUSLeqV7eqTE4jUMB0)rizy2q-thQb{s?h0Uk=D4@C#LtEo+?CPcLbu$U$=BN_>H#k z{&`Ou$At6wXXdZ%P_eqMwO(LVtoK5sL0MZkoU6dHCe-Cvh|I|rv#4!IINej`DsJQUj+!2+X>m5CYQ#Af$whFAoq ztHdScpPIZ2^I78ogB*rq=!1wB?V49JhzvvGnj)ql?}c&2DPp&(dB1^{2(4a=!0^H+#vK0}k%`vQ;!s z+oXU(j&nS2^DxxZNf{AUJnPp=zcyM^rr8T5g&K=}u53u{43-m2Sy8Z?ilbChmP9x5 z%NUc_&MuMmM>GOoFF2)4<%7mk=ZMFJWm>{=pi6HPRA8DZj#KZe0vLML)7$UAKQ%hq zCt896GdH=FcsB0 z0%Q3oP-?gS<@}*4T8(6_a;q}qV`s!n{vu%6`f(prBZ6Fd3)2#IYUC>8*te=H;S~OW|j` zYo*U%_lGzZatsn~)#a)TFE3LuAFyLikfx-6MU}T)I}anwixyD;MPSs+VoR zv;V?SgJ}OPtHF7z>HzUUsVkTzvxkP!xUjQM;h!HsHdN#*Zno2GT+8u}2AH+zx(Dge z(%0WX)QII84Yu;1r^C`@EHM?`5_jCU@zHDG%FDa`s2nc8;FdvLTWnXQXg4;JGnKDo zgCVo!<#kestlX?H=#A*Q&mq^39y$ci7V9dc{4Qh>Q62u{PdnnIeIqBoCfvY~Ge_7s zMN5u-k`D#BY`~ za!m{Sa`n+XdeSml8mY$J7UNioSe-HV4Yy`YO$y#O3-&O9f>(h1I7fSW1k7V19ujg; z?-q8AT|0bx!R|{RH?^a)Olw<2YF@zJ5^il8B}mwwf2G9lC#MpYXdYnu_{cSvxdcRk zUMX9PeFVtmU)#FIrYUzEKDe9#iia3&(C+5d%;@;J4$}|;4y-FB*sw^j%k2Ny&0p_p!83_wRss-p{zr|NRn|kR zgE1lf51Q$EOA(K4R?tzRlqg*@^);a6?_r3~>O-1$g7Ww|q%M`C?Y|TAKPRw)P2#!N zy&p+e1$z2Y5d$jIcxBKadx&V*RiR6l4x2ZK#R*Di_BkdoRW{wB^Z5!st>Dexwr#MldME=jx#OLG@rXw?~wp}_~ATS9xY7R_C<3J z4#t0st9vmS3o0;4gY~5)(%alW{+Rw;^?}0D$&TeXJSJ1rI#I;Af|rl|`a*E}R>?Hm z=lJ$x@kEvaq3j==uNat;Z=}42x06bOtq^HgVMy-6KF>VBijw=ssPSagVRhF49*wvCD>Q`b@#Q|Q@&y}50Bp7Q*0dsCI1xqo}MsX|#B z*E4Sv6QVB&l_KJ80z9O(e`FPyzPfa-h7f>XHkS}>#>Q*aFy{ms>=81 z4L+Q+3D+QX6+=lEuVy7OvVn0NH#JQTrp}{!di)aOgMk(GksP*A;)pOaQgA zH?pY2op|jxr%LS)r>Zm-)F}HQYFy_VlyJHWTWBQq>mxXCFF$bX3vSAU?r8I;%ZfcX z8^3`$Id+y#06+d{7zKR0`G-<<^M1K&ecyrK$XRLE^-2G6dhY9>G~Z7zTXKetJtU7n z4fvExs+cpV!uP3~v%A2i3}NQsMWf23=bW@9;X?wL>m=RY3zzX9oQs-)26oj=?T-;O z(}~eVh!^=uB`5uX`=gBY?hps-yU9d2swKDy=vFz>X}qWKIXae;_Bjnmpem}LHHoIWolr+ro%V~Oxm7m$(9sZ7rDf_a}HvZmSc+ljtra^KpyL6&k z3e69?P);~{HB-J!Uehc=_p2E1;d6$fV9@sdMj7=4XM$eT#aBvT71GSh)VzyW58-fu zJXgO{$0f9E?Kq7}4N_c$e4pZ&>`fK+w>I9^?GGTc)+cDFi~Up^ayBJ~G6ZX&;Cn1- z{>DoI7dPQ16lOayvem&P)*~y~ckiTI)lTgwN@FEuuRanSo%&;;FRX zsi~wm(!woYP3~b_JG-XsvtML1{p#yV=M#KN=6)aJt^hpe-b6O-zTlhg?KwD8et9q6 z7};E-Kx(F{-TqC-<`tAKym#}ocIO!&i*D?w>bsz1-26(ne9ZSm{b+BHw+PL~BaQIj zgHFyswojfZ-uvt1A$i|41Usy+x}ysbCf%{7PV=RcTVAbVRDrZ$<&srmrQp5EE^_Bz_LMBZkk1gGo%`wF$S8j#UQy3C>i!> zhk?h0Dmy*>FF@(7*kx^kVA4yM(g$746i6FVIWK#kTKbJ`w=E+|_buIwdqrDu&Rd7@ zVy4Kb5CP-;le_(E^=7R#V)h+s;0Y@hR7WGwBCp*#<(P4+YlNnLu zRQScVriBzHq4L-Rn6;4X9SliB^09rYS1TGxb<1m3bhmU1NDGJxV;~I@1Bnes zBi-EZh4+0w&+n~&IXHG4?7G;#*BPI4Q!LQq{&2Y^vV}DZI>Xg(SFg6nP#SeQA|nq* z-z`HLt{7qpxAw~c6<j^tlU+*OiV=QHrf$f=IfQKTq)vZ@g-r$igBTyOi zQWrdD5f{Kny`l^c)NOW}&1Q@@$A#CKG^K!^#Kfp`+0hOspV7ARChB8133-9(`NHvO+SdQ_+Oz zY;4w-l15T!I2Dr6Wkm_OAoxIJFAk6BZs%OkP_y2G!%C{f$ENQ&Y59^M zXQ2+`Ho;x8VScaXk@F3M>;4Xn%x&h&j#~@^&1)46zNz;=pa+;)vT%88A1=hTSLGne zoZNssCLfq-r0Hn;r7U^W~u}^wl*8s#VS^Yk4ZYUYJnFe||oFk221`xNN zwXA33e=pj{oVCTjB#-!%D);`%{TG{KBHq_|4=_5$xwP%+e!0H!XwB5%sxpaFyq3Dmj0K=;9{q-b^V%^=xJNem(8o(aM=|r(+uUwOCEW!J# zUrHZ9ilFqraGsE?oS6=Vq7I#{A9g-w&bX)xy=6$EMPAVUoWYS&Z*mN#6L|pQW9acy znwBY2E-3P)1K{aZMWaNVA7@v?P>;Idv!uDNS8?KwCipvFZ%*Ox*XYE&z{1-PBSSTy-oqVG#^pO6emphbcJ{Ra z_v!o3v&}u>5E_78|F+9u_iWvqyiM8?GTdAnV#tTRjW0jXVlp+awp$f2RfCB)N9j?{ zoticyHmWkPmSW}o|JB-=frrV!yZ|6mPdUu}PtyL=s;9z1H9cN$vXF+b$5-PdjRE+n zpZLRY9Tr#iIQTz1rxP{3rEm@U{9V=V@^e=mjq~y85yBF=99G3fAY*^4*(mRJiMlDp z+~R~a3xMn5bq)A~URq zu|{Erg3y;da_iqkbeAtPS$r9*S5KNLu-`zbJ&vKiJD92enR55H|JsqCmus4n6KsI; zUc6%6tI`Ao(sR$9ijHK*Mmct;hB+&^N+q1jcC%b|0pN$XTCEURkgi>K)BWsa{I;5g z1baSyu}HE{wFadANm?A=wqu$bX&TzvZl(oC6V+TCNeWr@@4sM3Q zAA3(IxdGLnJq@VLJ?Fmgkn;;WZ!{adt& z-2VtNf$OJx5`*#=dAV=Mx4w@){PFF#EC_ocK~qnZ(Nnq7)i7**y1_DofegKjd-Q<_`wQLqUaRP zf^+kK_u9@I*UUb%s?#hDUtia>T@6bzl=-#b8V)0gsko@6_TIeS?Lw=J){9>?R1pY@D!HKr+QmGM0OyUG`u)nXf^g;{LE z6t?Mxa|_=F#CVJ91}NnTX=L{L`Om?4&;_{V2`#9VdMQVyN}^#Xk_Vog42U8 zu58MS{vBGrfb2)%{`?E);D@cKU2wl$rSj})#ki$tO-jm`Fz|EJo%eg_Z0l4vaW;8V zF~KA}b1`+z^RU?+nb27kBr|djZ|{uG$NX zqMuy@&#sEZtvI)LyTcPBZw-@^x#{F0PDzBBS+>=E3cEl-fwM8wmbt<_n!~Uwm9x6I zkH~lLJ$)3eIu35Snj^ppJQB@Gq3F|fx|G^Oral#J0J(1517miM-a+YLV4J`}_WS68f#2%Do1Kty_d3N=9^MRr zaH{<&J~Vb3 z4N#D^N+@D%v4^La4*lljUBOhyBdMf!d5`6b3Fc}b4b4vts8JTZ^{9Td*%#?~Pe4V~ zCY*n$H=UmL&|M^YGT>C@!s`-hi^XIr4R(u7=I9Lk5HJJz+_6o^bqhYg#J{UMXErmBd$whCfftCGZp{#py)Xkd+3%I)$r17qt)$;R7|>HKLIys zt}0yRCqqrESY8Mp(aV^rI-4UaCz>Y;JgF6}`{!$mWXYrXc)1MXovDThj(%qg;PE>h zLqm$;3@Xm`IJnhk_FW=dT|%3fcw8B{`0**E5-O_W>VlY5Xd)Bq*u#D?Vdw-NeB!!! za)v9h)vuzU#ra(Hx_59tsg#a0P~8M?m|Nw0&`+I@|R#7uED>Y5U4p(zWweMj)Fjo*Jwc82-5nGJ+$c?-{8F&xI04F5j*L6;U2AbV!>5tHQj zTbn=kzK^LE0-B!q*2~gzbX-vc>XOkCC7R4@I=*pqAFt!kv2z?Gzpa$q3@2rYxX%wK z>o^vE*LoQ6bA!CjJSZI1C;zURH+J%q`W)}M{bN2oNh^7U;zudpm$$cBe=o#Et+Y)& zrCS~B;u(qx+i9M83d=PDqZ`=pqwX4`EccB*s$IGh@OWz&5iqihHF@K$v@c<>9pibh zsKHf3-rIJUxY0Em0p};R^rA!Fq@e-n`G;Hsn}!GwAe$dgjiKXa-^Q*{sKw5{#LTsf zB$viM^7^IB9?gj!blsnE@NTd&iAcZIp7%H&SfusdGI7=sn04fSbb!=aUpbo$>Ma{w zce1_&C*0$`w{&F?Y3VFCD$R6aKD))JFFtR~wD)PAp(bp29YQJag~hOVC2;nZ=>axT z*V!wDb6hA_sW8R|_%;P{4;Cmt%gDnHLM(S;fnzQIP6awa?v^xf=lWkb@Z=ZqniIUm zy!-wqSdAuE1P;Eb`6fV#d%=v$6RB*G_IdVl;dX6z!*DL2+)Y80LSs*|Pau@GLg{s? zp!q9S`k604Gn$Gl+8fuF#3b6-UL+=>5WAt63;_d*r_Xm+SB(&->Ven(DM(g0W z3fy2%a8SS#Ytn~;O$vK1w}7E;0mb*EeWDMe7rTHhTYCL}cWS>B{Xr)IODw9fQjE7X zZr!bCI<95#YFk zwbNJI0H`PB4mmohhNg&|#1F^2xr@QXAyLJIb!@a_l_nb{j~Z$0&d&cZmh zFBSPT$3KmBSGuKakt^j@f#4`yi(+TJ?jR-mNfnP+r;d*F8@g z4#m;O9_39agncX=1!9hH&gXOhTKYe8sb!RorZMsW;1P7|;$(93ZP}*hJPFWaap~ti z<+y!u34Ci(1}QIW?<(F#k!3M6woE>339@hAs7a*5hrHflGfE|*#(#ILd%Sx*s)mRX zKt@sHA1a3lNIY0vT5fgDQ6FmKNf>kpy7MZO;4Bqg`#3)4H(HHu`_$&7w!J!Ms^l!- zE65y=9;i2!n7^Sn^<FU!Ww`lW;WZR4t2 zO|d#r{v8Jx%rXk`5JI4>k#k|IYsD$#Rsby39f zG?-`4erk5H=P2-Cb7|@j)R-1MgSS-laV6>lY_t{3#*3 zNY1;+t}-ye`t9;)9 z{@%+=U*UIJNuR2^aG&m0?Gr82m|x8hT13v`@$ID4-nSE4@nH>GYyv5gLv;huk|bAj zH@Sq97`cdWXKw6HKdB$Q;?gs#OO*=|=*(rn8aF;jHMMsJ50pTt4-+d3Y8I5Wv)lm3 zk!EZpdr{sn=`(+i=#{WkmdBpWtbFsx?Z0p)8XP(lz^<0bF!T4T&w{zwk^NIsL-6T9 z=AJxy{$*cQo{VP`i3GM+3(#C>=8Bst-)bLt%&roGE068D_`N>gN$3O8K+q>E&_6Jl zINCnisX5&(T@UqU8jO)khfA3 zD3$+wK9_&1BC#~lFp`XsB!ZkOATF0%8z_q}#W2!X9IX%_i4+_Q7sZ}$BuMiXcA_|Y zfWhc~USKf#*bg{Q8uQSFFtieoN@x>t)?j<9uO~KSoYgcY`FnEx0NQDcerY(LN_uhA z!~W+}N0V=OT988|0H8uhLPh0DEc0Vr12p$gKHj}=^SI#@U@GyLYkciz4yxVXSQVkyjdk^Ei)h)@`kXqc6t3+nE>^$Iw*u*lbRbd?+UV!71Lx?r>OMNyXQX z19`5A-h%Z^N>-HjZW2KNH=|6IlF-nWhAHFjYadzsL>^8k%6 z>W^Ab=%q`)XGG}Hl(l4OfT(nD@}z(hLg;)=RQlstSY3M-5FBKEvh10iV#42D0)qQq zT~p`lZpb*n>xN}m?&O|HstBL;_Drw#!XdA z7hX#36q0$3^A7zoCbwAQmwq$?3}KlS!|5Hb@czZk!SW5$xWBAk2FRL_)pjSl|8L_Y!4V(XBUUXT4eOx z6vJFz1fsv5U7ZmH=7~0u4@P8bE1k#;xBhH7%X6e?J|y*ucEQ0dF!ejxLZ8}94~cp- zWpJF{l?W=SmftK*E-Z2TKkEibNRE#KTlh_vzpxUKs;?S5VRdNTI{}}s>n)iJo0Ghtgs+(9J}Kq# z-k0HVh)DJcS{&c1b9$=e`l=VhH%6|uXYku=lB&DXw_ea3?=Da7rix8Tt%Bg3(|&F* zL_s0qiOiKrceOGgWZ!p&{SmSs>KBJb)>%`ob?ZYDnQe< z)lBjOf*HvG1)5va6UBtSaz(jD3+1an=ADn)eeU(@!+Oc5ukllZgX^f+2^!PJn3muB zUuUnpck_iTLLYz2)>n(`{|l$POuFw2P|xmFnD85S=8x4LY>-=)*V{EGyXP?WgI)Nm z;@MgV8&;s3>3ylT`e9I0A-}UTzK(R}*imH9=!*AqJMj(G64o#HUFp17^!WlxtHF-t z!yCE^a&<|01igt5T4`7WJxhiPo{(x=+6)?~6j%=9QPB~ZP6WKCxtufm+pb+iT$m784^UUgM*c}>%7X*1^^n&-S84I ztg3qZ7Y>Uz@duZH^Hpgfnh#%z&@AR0mbc2kjEbdIWG6%q^k{f_P+M3WJWPW7C~Jx% zpZ2@_C`yj5ub}AGwhquHe8cE-3Oqo1fx$&1lmT5EYEpS}hBL$)1uXSwr*mS)!?7N* z3>V|%@K`0*t+6x!GPsQ%j&D9!ItKN=gI{+> zZfcuoW6w8imV%B!4Vc<L0#73lH!jmw6ycHawlI@b`0o7^+O(6N2H`aHM=8s zs=eI09ndJ+X`7XiPNVSRAtNg7oj2Y_i7>%)yFrL%Hd7&$Olxyb+#aybhsh9golbSZn2=K8)zE<|9T-lXCpU3V&W|6~9GIxvxp~p+Clw87l@hsRt#;ih z1#lT}8|FkfVL~GMM{xPq#cF-(C(;hYW1aVvxS*`sd+W`tP|EOW_nQ;A{oKR169gZu zRBR!x8VRm!$o$wUV=zpVd1A^qdxjUo4_g7=jEPwM1%eAWs))$pt8A*A*^mKG2I^Y1 z)?4|EFPgCfOOqWca_=;kR2~{fJ79d{Z=ag*oCrguO85aQ~j5c0Df5DgZi;?uBu{0Agfq|GsfP6#K8_+8J6Y#!h*lNZ?@J} zpK0zs8keC=h>>?Md;j*W@o}IBj*TkI(ls}R_tEiLm$0wD6=O694QCQweEW9C&er*Q zP#$M+PCPB(-$cHg-alI_%@i1ZzzpvFGS;PgCH$!QHrf?V)^B0O1uHb~@z3@FA~~X0 zpnXIlda}ImE1t6r5jLezTw|$})iy|TUgl_)sUm#aTjb=6K81D;g`)It|BKxU z1v}AXM+4>2QlZD|$ale|zt$==bL|_3WAT907z8$eH}+BKSJ|284pup?hHE*kMa^Bs zG`$qtaCY^p7Uj$g*B4ZI%xV{LmmO-#zEgo>(moXd52=MgbN=)Qf18v-Nkadz9%4;-^JH^@I=>>VpUekLW4YrE@ua)Q zrtGnrlDlZ+%47{sOL3x_ybLt>2z_P?EK3O{`;v9NJ&aaiw_vBK7A!jVKJ#Ap&l#=> zW9qz;lXx}ZJB$w#Z(Yz`S#Q!}9m#%kI3d!ChWqRm#mAQ;<-AU}D$$#W#LJsNoOBr_ zfM^*c<(*)aeIgRjDXg*}M#(1@f%ewgs~y1Zl+Gu(x}#HMw1koUo+EFzhW^$F|HA2? zl9YF3emf}rDuKSNc-+2%wF!|at7e`u?Sp<{J_x{c6i5rh;3^BWV$VPEQ1SMtTy6n4 zp$#3DLNS`p@?XBzyPaJ=5g8`OcE!9`kdmK0x=r9Uy+Y(sk@!kfmu^#}tEQ?u(a^(1 zaW*+C*<|eeGO+EV-Q}nfR?@p|p znwnj&MydjomYXZ` z@6(YtdnDP*ly~pDe7dmf6&v8>*L%l_+Sy&$s@P{HK}fTUsRX4KS>)9DyzELkKN_Ks zol9MULqj<4Qs%r1O28gZ%%y;ZTHbqcb#<-zrVikVV)e9Mo6nv`dD@*X#5%qf$q;O5 zrbbGWn8+>UC7d-@vf2!3xX$XtMB@JMAf*5Tib^21S=n}=OD|(+l=Je&TGn14=bkIz zIo!15Awx!sK)$@RNge}DC8>=|CnrY^iy41^U%o%Iqjjg?@q?#^Rhkh*IudNNk_jh1wWpIqvo4|8fEf`Cm=RtW=LrAg7SJD8~L^yDoxg=RZ*lkyXb4%6b2SaQg0#yG|AACDnMm_pC< zlfAC#^=ZG5TZx?s`M{UBq!K(sq6SRh2t!MrJN$nR=?k}bE7k9jqG_k@0$IH)}cdQ9=nkB1VvxXR@+;jA~@~wf82Wx4dwrfu|*if5w zR`PJYR+cg#_gzqd9LSGTmex71O^oG8uux<|^1lJ-Cif}o54L*uUfu@hmkAkE)^Mk` zdyBQPT8Jbz-F@**+TyXdIYz8D96no5=f6oqBKVnCo!pdOGq0e!x)Whp*F%7bPGZ6z zNdDje_1X?RgPT;v_o3-CaJa;zbq{uZ8+9$ovpft0e`d2wSx$1>Jl|i??ny47t7jb} z4(ggcgeH6{lUZ}5r595H1jVd<3&F(BtUp(+ynxyrzu?(N65_VgbGucm{;j@#`5Q05 z5I`Y*R^fxc!oflb+AaA9*S`-W2Go=78D7*w{O<(q6kU&L|2rfraW~X8n)?^fnKADU zdX^Ce)1={45Y*yTJ#Us7)9I$}Nx>0}9n1MLP`gukfS0y2jXe+g&61~@)QxWOW5DA9NsbX+PZGex&$VW>(xdFsJC9aa1S z9KcV9-wR`A>?G1%?-UD*4K;_0jYomMHqZ~B9**TY=q;c3@!v^tza6BGWc$~4c-tFh z^a1q-(i3GY(nfHoG&AQdure4&T1`XNdeLq})Lf9g0@LDcT)8}GYOqYBW}%J_m;}P~ zz#OLWJwheDy&?A;NuZ&YHN z)$8JcDY7dC^HCn*wQ8e`_CKz+t!A?)i75FnXJ*XV;O*T<{q8Es1b2MO5Rwtfc9~|n zJ6SQs?{wy)NS6toYmO*lyz5*0c4*FfX~MN9)kIp;L+dE|_|jWAasI_j3&yrFz^$&FMe@9)3GR0O5f4HBZ!kW^u!%`2%P~*HPisy;tDxG+4NdfM6U>4z@^64y z42mV?1BlwYTej~#AuNN)G;TO`dn>(uaV`H>gS0!FeC@1x*3lx*)$?U0O$|2F=Snk9{h;bL4Jy>=FBwX64&U7FhQ&5t!Z?JM1?J7Fb3`vryk` z%T!_UHyxgtH0-QO0mZ|(bmh&F=&o3KMlz;EWRx2D#pFzFvvm%EK1d(tCr?pR7{3V6 zK7E@B36uJJwf+$ff&By7f7SXO>)v2PAV1%K|Gn_^4im>XO`+1mqeCcO*Lx;DyHJr&pzCyEa#P#6P9km9`J3oadRFv#oY*oyi zdd{$qE2$??Prdy~zd|qWsAtvFlFFGw798HkK#Z6vT`Rx2xT60}Uy5k!_{86H>*A1y z2-xQ#%%PeXnM}h3dO*-eXT@PpvV;-(z9?DuEc)d+sg^n#CuXglHsh2Z)n)i|>xFSC8zX|{nJvmBi2~pIGQiecY1G%()A`Ap4-(}_~B=3Ft zbZM+hSoPhNi%`0A{RsHg7n_-8h*UxE0`l#y{o*pTU_Gev`pm@NzKGr_uGB01G}qUJ zOA^cF1>Er!yZ4^=sil?%?;13H_!V(@vuOTHLLdY2#d^+=-g5UAC+t~CteU6ruiVdU z8?HVx(4OVmc<2WA1dzQvTe8_VHH|wR2;Sb>3Q%S2?pMBN=;xd5W|# zi9faNyY~?}&V+#qsL=s2g>{bXW9pT(+wzhRq9^hCd*~U+3l4))BWGU^@MIK8Wb3)l zLMwPLJogQVZC=lAyI9QW$Y8(&AAnE{9g)}>zj&E-AG)WR5IkDeQ`)?D$!#c0+YZJ) zW#9YUV9wlXM#@T+2?3uSje9^CqaV1Vfh+U(xFQrr5;BOf8i5@<|ApgJA*9@MD<(o( zB67vQ%h8D2GRYM^h%Z<#qTaK65$jH$hCeXoA}b=s?iK@IM5dXFY+v z?;H}leZlXA?rjtgL47HNvyC&(^giTVR46?ZH@2ttFU({4k?6;9=#T;P6|q@P2_YbD zvlF81G@!pPYukkwbHq~1;IK=`av7RBVFU15*`xj7&$CNBHRT5Aj9~A*i1^{)ZM#o2 zxYiHkmTnR;#a%3Lc@cTY6;oblKNJed<>!2EgBtEcIjSR)r8*=2R2P078uuv0J_w${o}%X%z;9R0M{phj`kY zHrf$;!Y0%*E*ojKJRu+MP92U*>bbi2zZUwL@W9Z=RNU+ks@d}Nfwm3DEzP)C7}i&| z^qWb^#&m|XKd@1%p8fYm=_CwLpNT;y9^lSj&;H=fYcxk_$y%xS9?F<0?&Y+LKa0g( z$m0*!K_xP3om{h0$ViI+$583j+5ZJgF#upG#MpfI_B)6-7flm7)ms3lB-W&tWB@KG zBZuO{gP-O1v$0V@qyddNSKLcU(l92+E&0{GiT8i7=I@L;2{L*KrL8%Ovu`-B(z~CF zerl#^`i14Gd)0cg<=!o5l-l)fspJD|^+-xVH4E)`IRGt%rDzeEsO8}@4_rC{YYqCWzuMBHK0+eq4R(>v?WTSm>-~}-1G_8 zO1u`FOHR&LT7`Um1vdp(3wNJ+jbF*XBp3XAtF!H5CusW2 zPD5aM5Z6d8%FAHWfAuzkMt)hz{8nW#?w6B->wVI)^m`5feR~K#>zZ|Sgx01hz!1?- z{_K-d0e6cLd!qMlV#}9%a-yf(7n4`B{w8yJRr2F(f>QJswRz3YZbC+2f)p1 z3V$yYY#q1mZi}k89GS;6Mn`17z317;9D*LEojl}!G5zZx=hG!WALJEW{`*HJp4OC+ zH*&b##^)QE{>f(o$OLwEYpOKR?=5sfcRUiBXt}M$6v4_p-@f~`XI~9<_Dgiz9|Efz z;9+4L6^MM~=iLrG;gvL9x)I&62bH`ennt~(H*71 zG+ALXJg$!p^88L!w!Qlr`Dv~Wg}%iq_9mamZmRq2Xe<{55qW^*NkYc=#ZbEtm58L%xM8+7!y)JE4j1?#pcd;8aEf%{WFh72p{;^7#-1|*DEkYVe7NNa} zkk(xQ9C`#{)Q|Em!uUYQUgmvsObIO&Zd^|h(f&gTeXwo5-JSeJ&Fs^=``&FqiehbJ z5N6>L_p|p$nV0z^HgdKfS9Gm+=sLuLkHhJqo@GU8(aMydL&Ck|^NHTcR_3j2b z}EjdmxH8lQ1l=x-GkmOeFA0?W%PbgZLO zVZ&hv$mLgfsgodjErvBFmGVkUy?(3Bkb4MXF{brVUlY=5&H{1WX6G1q1YQ`zsPM`L zuiNlrRr4yCdgv-U?SW$x%8xF$bp=j|FGJ0yto|2{t12kXPN(MQj*u_h$j8kL;du83 zWUP|qhG~9)Fg5CA#KKc7cSm4(mBm8-@heaV?A~)x;GPy0!8*4b%nvEiWW!#>_o$P> z{h>iWn?&|e-{D{>lp?jR!l?6}H#YIA?fW6M#bC$qw&kbzGGbR8)jQD3u9`MFWg3=? zngar{?4)ol2eslAeyon*QHH=rm0?cPsm{tdC8@t~%$QY)a(RA8u;T3d@1J{4P;s*n z$;%j>bTz@f62IcjMU>(`$rT@(D%cfx&EvMV{d+R)_kXMh(SzGH_xnN!3?GobsWed; z=AP{`#W@SDck+VNk0jt1m@#_&N%(6ovsh%-Jh@9pmZk>x$$<5%*U4;Bg`C?BHkH*z zwI4G)l_L^Urvpyg>n6*GGU~L~5&yFPD%e$PNBZ197y+dkFe4qc>In5-Lp3T{s+r9Z zNy;smdAUPK?G{PV;)!Jc?)M#+s2wFmp>>rM*U`bO&X3N=A_Yrgl40bJY=SyC+Z zn#dKEH4bdo>q z4<`zVGQJ~efv$y$pKZhKXRl_NO~i+riZE<~$+pC;!&6`Lx#9BW))He+RmH^I_t%hM zkDQU~H(i0+cXyC2QLD58u{Sm>NOlAZXzCiZgu#Cxlfv3X9w zLJEP?D5dW2qY@bQw!bg*ZEk@hr*jq-@hqhQ4!$LM&hX^ezb$DzbTy`}*ikpoZ@r~H znb}k3shrRGDuH~AP%jU#{xf{^qY1&L`}*xe7MU??7wsJ*uic;kbMz{Sq=yaD!}Hp_ zrkXH^8eE~27Zx|V`#U-z6Qxp5@~py)k3cZH2Hsobm= z>Vj@Q9^-W#a=teo80Q@Ff z_dkFx4>~4*+iBZNeD(dgaaL1*?fr|(L#`;~vvHwg@9(7MVS>!+c4g$k1dbgoi*xAt zsx4i*v4~_UoduWX;b)J7+EBlauyGD|^eQSJ`H43g4t?Ak9`CTC-rcH4A9VCWEg0c? z%S+CKld)-zd*wzyAr_aZ(LKxrJv1eHPV5|t96+@ACr0X7vNr_y05cc~ZxWjpq^soz zF1?}GMw zJrkvLfk3~b8E?Q}a6TK!=urL+{E4KDmIVMu@6M5*VzD`~z5;D5SwB_qaPnyt7+y(+n5q@Sd%r z|AH7~GQRKU!u(ap!p`~XJs3df(GgU+ z!LfW`g1W7g?)n!_F_YK~U*`=9n^Rl9uFi(tgKGzb6n;51j53Z}8j9U1b#$?qEpk1~ zUDh%k`luU-^<6%hw3yY2>c4z@$#2eE^2INPECvX6=_^!Mkg|U^oyxHr@Vnp!31p2& ztTX>ynWk*najKvhBJ1Aj6S7E_Z`7)2JQ6lh3VRsJENl*qp2&Y!)+O3FG~aGjDKj)B zm_+$gsxVb&@Qr8nTs1mYSqZM8m#Qx$7&0jrekYJSbx?h+;DT?uJ2@f-|64_Hp^B`#QL>i)ig=VhwxFXU1`?wKwH? z;5qd}!0B}yUD8az=%X~*K-L*77OP+N!KrFBjqHgz=10il0l2RIu|-TuFjK9M+pe#I z1uXGf12Q3$zCwy)zXvu`8oFl%NSAJ!&pG}vBGP-dhX40J*#Oi-bM#@bk(K8GbJVh? zLuMtWsBxrKgb%Weh+`>@kMFmf#)Y!qdE4?MN(mA5@CD?*Uc^u?88V9v$lk=%{ASq?R&*E0i_MEt=~lCjWl0K4P#ofOhKxz~*nw z!bi3;?aRwDfHDis6hWS+E3txaV(PRNKm69$TFq96Dbiw)qiHOdrFfjX~+t*WMPgnUF&gFlu+1gYbIO*KK;W%zY+I&5;K2OEr;zW6N#Mr86e}cQ`?*Q z$1}&REJe`~4fJZEzeLLn7?Mx63gwTqMcNqXRmu;9kYqxGADf8rWG#Vciwf|0fR8jO zSIoEQidQSS&>i~Fp$dr$T-?ZjbI3SU8Qe9c7!G{C!A@w|K2ry`p`Luf8^=ZbM3=#| z=7Wp;T4u@Y%(SZIMYnS6|;u%bMF-ICl#=QE^mq>?9T$Rr1(l&i!)7Mrl?GR$10o z&l4~G!Y15@g7J69vfp>HqjXpDn!zd>Ta1B0JEwN@Kc>SRyP-US7JdB>O?aO3PCZ?C zffaiEFKoW4Duso#SmKnO@LQ2e5qYEHu615l0HN6+6asVaiQW5?eV=gUH&Jyl3)cMN z1?)_Pm4>*RALDY9v>O0p&xt=&JpgnFuqONm9a8T1J@ShyIi#Q>P<6bsAeDO0GMGn3 zf-jfi+)x@k;E z8ZMd6P9ABQ&FrP>Xi7NV1Dz3ExtLliS-rd(iut)+*YX0NzS9q{Q_?0 zXg*V0rp5^v$Oo8N)oXR~ceAFJd^z!U7ytum!o?z29?O=iwP2c`LRy`Cbt*7!RLKxNF*j<#yb%aHRA^4VGW{Ed2E&pEj|GV8-6{ug2Y9Sv98w{hbl1R;cojNV(K zjUdV>(R&x2M2+5}w6_#C|{qe7F{rXFeD#va~8874d+I7{!T-S0~J!qV{M<&r!ge5v~^@-Hl+ zF)&UI0)pL9K`L@}NF8RdoNNHc3EoR8a#+}w99YYv`7wZ#sNSh#D;~eQ4yvE?acBE8 zv5-t|nOMCXF8$afItbZ$C2�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

*jg%;bp;u_!8xZ8a>bkB{SKq!NR+! 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

}IHpVHxOJfgFPOuhSjEho_-- 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 T?!^;=nvyy*e4tiI8`S>-{k`RF literal 0 HcmV?d00001 From d3f83eca44027870bfc441b41c75e2065b546889 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 2 Aug 2022 07:19:34 +0000 Subject: [PATCH 410/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index b9d49dfc3..3aac1b07b 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b9d49dfc36dae9b8b1bf91c2a8e8179ca554abdc +Subproject commit 3aac1b07be05b58217c2ebfecafff908dadb2044 From 2c9537b98e1186323f381f24e57aa104fdb04180 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 21:29:09 +0000 Subject: [PATCH 411/854] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/python-jsonschema/check-jsonschema: 0.16.2 → 0.17.1](https://github.com/python-jsonschema/check-jsonschema/compare/0.16.2...0.17.1) - [github.com/Lucas-C/pre-commit-hooks: v1.2.0 → v1.3.0](https://github.com/Lucas-C/pre-commit-hooks/compare/v1.2.0...v1.3.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 25601d8b8..125b1e931 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,11 +20,11 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.16.2 + rev: 0.17.1 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.2.0 + rev: v1.3.0 hooks: - id: forbid-tabs exclude_types: From 391a4d8883c5d92053df59a15cb82c4318fbc680 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 23 Jul 2022 22:12:34 -0700 Subject: [PATCH 412/854] use dfhack-config/lua.history instead of lua.history --- library/LuaTools.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index cef46c053..fea90b394 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1145,7 +1145,7 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, return false; if (!hfile) - hfile = "lua.history"; + hfile = "dfhack-config/lua.history"; if (!prompt) prompt = "lua"; From 98f8fcd0688e77a685194375842e9c207897ea14 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 23 Jul 2022 22:12:58 -0700 Subject: [PATCH 413/854] move liquids.history to dfhack-config/liquids.history --- plugins/liquids.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 5dd64812f..76c098c8a 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -55,6 +55,7 @@ using namespace df::enums; DFHACK_PLUGIN("liquids"); REQUIRE_GLOBAL(world); +static const char * HISTORY_FILE = "dfhack-config/liquids.history"; CommandHistory liquids_hist; command_result df_liquids (color_ostream &out, vector & parameters); @@ -62,7 +63,7 @@ command_result df_liquids_here (color_ostream &out, vector & parameters DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - liquids_hist.load("liquids.history"); + liquids_hist.load(HISTORY_FILE); commands.push_back(PluginCommand( "liquids", "Place magma, water or obsidian.", df_liquids, true, @@ -80,7 +81,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Sat, 23 Jul 2022 22:13:19 -0700 Subject: [PATCH 414/854] move tiletypes.history to dfhack-config/tiletypes.history --- plugins/tiletypes.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index 7f39ddd4e..b8bbc8d04 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -74,7 +74,7 @@ static const struct_field_info tiletypes_options_fields[] = { }; struct_identity tiletypes_options::_identity(sizeof(tiletypes_options), &df::allocator_fn, NULL, "tiletypes_options", NULL, tiletypes_options_fields); - +static const char * HISTORY_FILE = "dfhack-config/tiletypes.history"; CommandHistory tiletypes_hist; command_result df_tiletypes (color_ostream &out, vector & parameters); @@ -84,7 +84,7 @@ command_result df_tiletypes_here_point (color_ostream &out, vector & pa DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - tiletypes_hist.load("tiletypes.history"); + tiletypes_hist.load(HISTORY_FILE); commands.push_back(PluginCommand("tiletypes", "Paint map tiles freely, similar to liquids.", df_tiletypes, true)); commands.push_back(PluginCommand("tiletypes-command", "Run tiletypes commands (seperated by ' ; ')", df_tiletypes_command)); commands.push_back(PluginCommand("tiletypes-here", "Repeat tiletypes command at cursor (with brush)", df_tiletypes_here)); @@ -94,7 +94,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Tue, 26 Jul 2022 10:21:09 -0700 Subject: [PATCH 415/854] move dfhack.history to dfhack-config/dfhack.history --- library/Core.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 12faa9f7c..1cd00d3aa 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1469,12 +1469,14 @@ void fInitthread(void * iodata) // A thread function... for the interactive console. void fIOthread(void * iodata) { + static const char * HISTORY_FILE = "dfhack-config/dfhack.history"; + IODATA * iod = ((IODATA*) iodata); Core * core = iod->core; PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr; CommandHistory main_history; - main_history.load("dfhack.history"); + main_history.load(HISTORY_FILE); Console & con = core->getConsole(); if (plug_mgr == 0) @@ -1515,7 +1517,7 @@ void fIOthread(void * iodata) { // a proper, non-empty command was entered main_history.add(command); - main_history.save("dfhack.history"); + main_history.save(HISTORY_FILE); } auto rv = core->runCommand(con, command); From 7e3acc410e5030883f5d2aea06c0f2abfe9b4b45 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 26 Jul 2022 10:40:10 -0700 Subject: [PATCH 416/854] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 242ec37da..ce61d31d3 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - Init scripts: ``dfhack.init`` and other init scripts have moved to ``dfhack-config/init/``. If you have customized your ``dfhack.init`` file and want to keep your changes, please move the part that you have customized to the new location at ``dfhack-config/init/dfhack.init``. If you do not have changes that you want to keep, do not copy anything, and the new defaults will be used automatically. +- History files: ``dfhack.history``, ``tiletypes.history``, ``lua.history``, and ``liquids.history`` have moved to the ``dfhack-config`` directory. If you'd like to keep the contents of your current history files, please move them to ``dfhack-config``. - `keybinding`: support backquote (\`) as a hotkey - `manipulator`: add a library of useful default professions - `manipulator`: move professions configuration from ``professions/`` to ``dfhack-config/professions/`` to keep it together with other dfhack configuration. If you have saved professions that you would like to keep, please manually move them to the new folder. From 5b26d3361bee3e24ae95840f6be65f8eced5e7d9 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 3 Aug 2022 07:17:28 +0000 Subject: [PATCH 417/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index a04727cc4..dc118c5e9 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a04727cc4ec350399ce4d2ded4425f33c50cea50 +Subproject commit dc118c5e90aea6181a290e4bbf40e7f2974fb053 diff --git a/scripts b/scripts index 3aac1b07b..6a66be4fa 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 3aac1b07be05b58217c2ebfecafff908dadb2044 +Subproject commit 6a66be4facd1d5a7b3ab4cb61c34c678d19e0795 From 0096f7c88279e71af9e6ac23149de7534692a9e3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 23:42:59 -0700 Subject: [PATCH 418/854] split autonestbox out from zone --- plugins/CMakeLists.txt | 1 + plugins/autonestbox.cpp | 418 ++++++++++++++++++++++++++++++++++++ plugins/lua/autonestbox.lua | 51 +++++ plugins/zone.cpp | 234 +------------------- 4 files changed, 472 insertions(+), 232 deletions(-) create mode 100644 plugins/autonestbox.cpp create mode 100644 plugins/lua/autonestbox.lua diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4a4b74eaa..6ef5b59db 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -92,6 +92,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(autolabor autolabor.cpp) dfhack_plugin(automaterial automaterial.cpp LINK_LIBRARIES lua) dfhack_plugin(automelt automelt.cpp) + dfhack_plugin(autonestbox autonestbox.cpp LINK_LIBRARIES lua) dfhack_plugin(autotrade autotrade.cpp) dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua) dfhack_plugin(burrows burrows.cpp LINK_LIBRARIES lua) diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp new file mode 100644 index 000000000..6c878806c --- /dev/null +++ b/plugins/autonestbox.cpp @@ -0,0 +1,418 @@ +// - full automation of handling mini-pastures over nestboxes: +// go through all pens, check if they are empty and placed over a nestbox +// find female tame egg-layer who is not assigned to another pen and assign it to nestbox pasture +// maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds +// state and sleep setting is saved the first time autonestbox is started (to avoid writing stuff if the plugin is never used) + +#include "df/building_cagest.h" +#include "df/building_civzonest.h" +#include "df/building_nest_boxst.h" +#include "df/general_ref_building_civzone_assignedst.h" +#include "df/world.h" + +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" + +#include "modules/Buildings.h" +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/Persistence.h" +#include "modules/Units.h" +#include "modules/World.h" + +using std::string; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("autonestbox"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); + +static const string autonestbox_help = + "Assigns unpastured female egg-layers to nestbox zones.\n" + "Requires that you create pen/pasture zones above nestboxes.\n" + "If the pen is bigger than 1x1 the nestbox must be in the top left corner.\n" + "Only 1 unit will be assigned per pen, regardless of the size.\n" + "The age of the units is currently not checked, most birds grow up quite fast.\n" + "When called without options autonestbox will instantly run once.\n" + "Usage:\n" + "\n" + "enable autonestbox\n" + " Start checking for unpastured egg-layers and assigning them to nestbox zones.\n" + "autonestbox now\n" + " Run a scan and assignment cycle right now. Does not require that the plugin is enabled.\n" + "autonestbox ticks \n" + " Change the number of ticks between scan and assignment cycles when the plugin is enabled.\n" + " The default is 6000 (about 8 days)\n"; + +namespace DFHack { + DBG_DECLARE(autonestbox, status); + DBG_DECLARE(autonestbox, cycle); +} + +static const string CONFIG_KEY = "autonestbox/config"; +static PersistentDataItem config; +enum ConfigValues { + IS_ENABLED = 0, + CYCLE_TICKS = 1, +}; +static int get_config_val(int index) { + if (!config.isValid()) + return -1; + return config.ival(index); +} +static bool set_config_val(int index, int value) { + if (!config.isValid()) + return false; + config.ival(index) = value; + return true; +} + +static bool did_complain = false; // avoids message spam +static size_t cycle_counter = 0; // how many ticks since the last cycle + +struct autonestbox_options { + // whether to display help + bool help = false; + + // whether to run a cycle right now + bool now = false; + + // how many ticks to wait between automatic cycles, -1 means unset + int32_t ticks = -1; + + static struct_identity _identity; +}; +static const struct_field_info autonestbox_options_fields[] = { + { struct_field_info::PRIMITIVE, "help", offsetof(autonestbox_options, help), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "now", offsetof(autonestbox_options, now), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ticks", offsetof(autonestbox_options, ticks), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::END } +}; +struct_identity autonestbox_options::_identity(sizeof(autonestbox_options), &df::allocator_fn, NULL, "autonestbox_options", NULL, autonestbox_options_fields); + +static command_result df_autonestbox(color_ostream &out, vector ¶meters); +static void autonestbox_cycle(color_ostream &out); + +static void init_autonestbox(color_ostream &out) { + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) + config = World::AddPersistentData(CONFIG_KEY); + + if (get_config_val(IS_ENABLED) == -1) { + set_config_val(IS_ENABLED, 0); + set_config_val(CYCLE_TICKS, 6000); + } + + if (is_enabled) + set_config_val(IS_ENABLED, 1); + else + is_enabled = (get_config_val(IS_ENABLED) == 1); + did_complain = false; +} + +static void cleanup_autonestbox(color_ostream &out) { + is_enabled = false; +} + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand( + "autonestbox", + "Auto-assign egg-laying female pets to nestbox zones.", + df_autonestbox, + false, + autonestbox_help.c_str())); + + init_autonestbox(out); + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { + if (!Maps::IsValid()) { + out.printerr("Cannot run autonestbox without a loaded map.\n"); + return CR_FAILURE; + } + + if (enable != is_enabled) { + is_enabled = enable; + if (is_enabled) + init_autonestbox(out); + else + cleanup_autonestbox(out); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + cleanup_autonestbox(out); + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + switch (event) { + case DFHack::SC_MAP_LOADED: + init_autonestbox(out); + break; + case DFHack::SC_MAP_UNLOADED: + cleanup_autonestbox(out); + break; + default: + break; + } + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (is_enabled && ++cycle_counter >= (size_t)get_config_val(CYCLE_TICKS)) + autonestbox_cycle(out); + return CR_OK; +} + +static bool get_options(color_ostream &out, + autonestbox_options &opts, + const vector ¶meters) +{ + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, parameters.size() + 2) || + !Lua::PushModulePublic( + out, L, "plugins.autonestbox", "parse_commandline")) { + out.printerr("Failed to load autonestbox Lua code\n"); + return false; + } + + Lua::Push(L, &opts); + for (const string ¶m : parameters) + Lua::Push(L, param); + + if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) + return false; + + return true; +} + +static command_result df_autonestbox(color_ostream &out, vector ¶meters) { + CoreSuspender suspend; + + if (!Maps::IsValid()) { + out.printerr("Cannot run autonestbox without a loaded map.\n"); + return CR_FAILURE; + } + + autonestbox_options opts; + if (!get_options(out, opts, parameters) || opts.help) + return CR_WRONG_USAGE; + + if (opts.ticks > -1) { + set_config_val(CYCLE_TICKS, opts.ticks); + INFO(status,out).print("New cycle timer: %d ticks.\n", opts.ticks); + } + else if (opts.now) { + autonestbox_cycle(out); + } + return CR_OK; +} + +///////////////////////////////////////////////////// +// autonestbox cycle logic +// + +static bool isEmptyPasture(df::building *building) { + if (!Buildings::isPenPasture(building)) + return false; + df::building_civzonest *civ = (df::building_civzonest *)building; + return (civ->assigned_units.size() == 0); +} + +static bool isFreeNestboxAtPos(int32_t x, int32_t y, int32_t z) { + for (auto building : world->buildings.all) { + if (building->getType() == df::building_type::NestBox + && building->x1 == x + && building->y1 == y + && building->z == z) { + df::building_nest_boxst *nestbox = (df::building_nest_boxst *)building; + if (nestbox->claimed_by == -1 && nestbox->contained_items.size() == 1) { + return true; + } + } + } + return false; +} + +static df::building* findFreeNestboxZone() { + for (auto building : world->buildings.all) { + if (isEmptyPasture(building) && + Buildings::isActive(building) && + isFreeNestboxAtPos(building->x1, building->y1, building->z)) { + return building; + } + } + return NULL; +} + +static bool isInBuiltCage(df::unit *unit) { + for (auto building : world->buildings.all) { + if (building->getType() == df::building_type::Cage) { + df::building_cagest* cage = (df::building_cagest *)building; + for (auto unitid : cage->assigned_units) { + if (unitid == unit->id) + return true; + } + } + } + return false; +} + +// check if assigned to pen, pit, (built) cage or chain +// note: BUILDING_CAGED is not set for animals (maybe it's used for dwarves who get caged as sentence) +// animals in cages (no matter if built or on stockpile) get the ref CONTAINED_IN_ITEM instead +// removing them from cages on stockpiles is no problem even without clearing the ref +// and usually it will be desired behavior to do so. +static bool isAssigned(df::unit *unit) { + for (auto ref : unit->general_refs) { + auto rtype = ref->getType(); + if(rtype == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED + || rtype == df::general_ref_type::BUILDING_CAGED + || rtype == df::general_ref_type::BUILDING_CHAIN + || (rtype == df::general_ref_type::CONTAINED_IN_ITEM && isInBuiltCage(unit))) { + return true; + } + } + return false; +} + +static bool isFreeEgglayer(df::unit *unit) +{ + return Units::isActive(unit) && !Units::isUndead(unit) + && Units::isFemale(unit) + && Units::isTame(unit) + && Units::isOwnCiv(unit) + && Units::isEggLayer(unit) + && !isAssigned(unit) + && !Units::isGrazer(unit) // exclude grazing birds because they're messy + && !Units::isMerchant(unit) // don't steal merchant mounts + && !Units::isForest(unit); // don't steal birds from traders, they hate that +} + +static df::unit * findFreeEgglayer() { + for (auto unit : world->units.all) { + if (isFreeEgglayer(unit)) + return unit; + } + return NULL; +} + +static df::general_ref_building_civzone_assignedst * createCivzoneRef() { + static bool vt_initialized = false; + + // after having run successfully for the first time it's safe to simply create the object + if (vt_initialized) { + return (df::general_ref_building_civzone_assignedst *) + df::general_ref_building_civzone_assignedst::_identity.instantiate(); + } + + // being called for the first time, need to initialize the vtable + for (auto creature : world->units.all) { + for (auto ref : creature->general_refs) { + if (ref->getType() == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED) { + if (strict_virtual_cast(ref)) { + vt_initialized = true; + // !! calling new() doesn't work, need _identity.instantiate() instead !! + return (df::general_ref_building_civzone_assignedst *) + df::general_ref_building_civzone_assignedst::_identity.instantiate(); + } + } + } + } + return NULL; +} + +static bool assignUnitToZone(color_ostream &out, df::unit *unit, df::building *building) { + // try to get a fresh civzone ref + df::general_ref_building_civzone_assignedst *ref = createCivzoneRef(); + if (!ref) { + ERR(cycle,out).print("Could not find a clonable activity zone reference!" + " You need to pen/pasture/pit at least one creature" + " before autonestbox can function.\n"); + return false; + } + + ref->building_id = building->id; + unit->general_refs.push_back(ref); + + df::building_civzonest *civz = (df::building_civzonest *)building; + civz->assigned_units.push_back(unit->id); + + INFO(cycle,out).print("Unit %d (%s) assigned to nestbox zone %d (%s)\n", + unit->id, Units::getRaceName(unit).c_str(), + building->id, building->name.c_str()); + + return true; +} + +static size_t countFreeEgglayers() { + size_t count = 0; + for (auto unit : world->units.all) { + if (isFreeEgglayer(unit)) + ++count; + } + return count; +} + +static size_t assign_nestboxes(color_ostream &out) { + size_t processed = 0; + df::building *free_building = NULL; + df::unit *free_unit = NULL; + do { + free_building = findFreeNestboxZone(); + free_unit = findFreeEgglayer(); + if (free_building && free_unit) { + if (!assignUnitToZone(out, free_unit, free_building)) { + DEBUG(cycle,out).print("Failed to assign unit to building.\n"); + return processed; + } + ++processed; + } + } while (free_unit && free_building); + + if (free_unit && !free_building) { + static size_t old_count = 0; + size_t freeEgglayers = countFreeEgglayers(); + // avoid spamming the same message + if (old_count != freeEgglayers) + did_complain = false; + old_count = freeEgglayers; + if (!did_complain) { + stringstream ss; + ss << freeEgglayers; + string announce = "Not enough free nestbox zones found! You need " + ss.str() + " more."; + Gui::showAnnouncement(announce, 6, true); + out << announce << endl; + did_complain = true; + } + } + return processed; +} + +static void autonestbox_cycle(color_ostream &out) { + // mark that we have recently run + cycle_counter = 0; + + size_t processed = assign_nestboxes(out); + if (processed > 0) { + stringstream ss; + ss << processed << " nestboxes were assigned."; + string announce = ss.str(); + Gui::showAnnouncement(announce, 2, false); + out << announce << endl; + // can complain again + // (might lead to spamming the same message twice, but catches the case + // where for example 2 new egglayers hatched right after 2 zones were created and assigned) + did_complain = false; + } +} diff --git a/plugins/lua/autonestbox.lua b/plugins/lua/autonestbox.lua new file mode 100644 index 000000000..f7140b0b7 --- /dev/null +++ b/plugins/lua/autonestbox.lua @@ -0,0 +1,51 @@ +local _ENV = mkmodule('plugins.autonestbox') + +local argparse = require('argparse') + +local function is_int(val) + return val and val == math.floor(val) +end + +local function is_positive_int(val) + return is_int(val) and val > 0 +end + +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 parse_commandline(opts, ...) + local positionals = process_args(opts, {...}) + + if opts.help then return end + + local in_ticks = false + for _,arg in ipairs(positionals) do + if in_ticks then + arg = tonumber(arg) + if not is_positive_int(arg) then + qerror('number of ticks must be a positive integer: ' .. arg) + else + opts.ticks = arg + end + in_ticks = false + elseif arg == 'ticks' then + in_ticks = true + elseif arg == 'now' then + opts.now = true + end + end + + if in_ticks then + qerror('missing number of ticks') + end +end + +return _ENV diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 385d79728..6353bfd35 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -112,7 +112,6 @@ REQUIRE_GLOBAL(ui_menu_width); using namespace DFHack::Gui; command_result df_zone (color_ostream &out, vector & parameters); -command_result df_autonestbox (color_ostream &out, vector & parameters); command_result df_autobutcher(color_ostream &out, vector & parameters); DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable); @@ -201,19 +200,6 @@ const string zone_help_examples = " well, unless you have a mod with egg-laying male elves who give milk...\n"; -const string autonestbox_help = - "Assigns unpastured female egg-layers to nestbox zones.\n" - "Requires that you create pen/pasture zones above nestboxes.\n" - "If the pen is bigger than 1x1 the nestbox must be in the top left corner.\n" - "Only 1 unit will be assigned per pen, regardless of the size.\n" - "The age of the units is currently not checked, most birds grow up quite fast.\n" - "When called without options autonestbox will instantly run once.\n" - "Options:\n" - " start - run every X frames (df simulation ticks)\n" - " default: X=6000 (~60 seconds at 100fps)\n" - " stop - stop running automatically\n" - " sleep X - change timer to sleep X frames between runs.\n"; - const string autobutcher_help = "Assigns your lifestock for slaughter once it reaches a specific count. Requires\n" "that you add the target race(s) to a watch list. Only tame units will be\n" @@ -277,27 +263,19 @@ command_result init_autobutcher(color_ostream &out); command_result cleanup_autobutcher(color_ostream &out); command_result start_autobutcher(color_ostream &out); -command_result init_autonestbox(color_ostream &out); -command_result cleanup_autonestbox(color_ostream &out); -command_result start_autonestbox(color_ostream &out); - /////////////// -// stuff for autonestbox and autobutcher +// stuff for autobutcher // should be moved to own plugin once the tool methods it shares with the zone plugin are moved to Unit.h / Building.h command_result autoNestbox( color_ostream &out, bool verbose ); command_result autoButcher( color_ostream &out, bool verbose ); -static bool enable_autonestbox = false; static bool enable_autobutcher = false; static bool enable_autobutcher_autowatch = false; -static size_t sleep_autonestbox = 6000; static size_t sleep_autobutcher = 6000; -static bool autonestbox_did_complain = false; // avoids message spam static PersistentDataItem config_autobutcher; -static PersistentDataItem config_autonestbox; DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { @@ -306,14 +284,11 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan case DFHack::SC_MAP_LOADED: // initialize from the world just loaded init_autobutcher(out); - init_autonestbox(out); break; case DFHack::SC_MAP_UNLOADED: - enable_autonestbox = false; enable_autobutcher = false; // cleanup cleanup_autobutcher(out); - cleanup_autonestbox(out); break; default: break; @@ -323,18 +298,8 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { - static size_t ticks_autonestbox = 0; static size_t ticks_autobutcher = 0; - if(enable_autonestbox) - { - if(++ticks_autonestbox >= sleep_autonestbox) - { - ticks_autonestbox = 0; - autoNestbox(out, false); - } - } - if(enable_autobutcher) { if(++ticks_autobutcher >= sleep_autobutcher) @@ -2242,138 +2207,6 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_OK; } -//////////////////// -// autonestbox stuff - -command_result df_autonestbox(color_ostream &out, vector & parameters) -{ - CoreSuspender suspend; - - bool verbose = false; - - for (size_t i = 0; i < parameters.size(); i++) - { - string & p = parameters[i]; - - if (p == "help" || p == "?") - { - out << autonestbox_help << endl; - return CR_OK; - } - if (p == "start") - { - autonestbox_did_complain = false; - start_autonestbox(out); - return autoNestbox(out, verbose); - } - if (p == "stop") - { - enable_autonestbox = false; - if(config_autonestbox.isValid()) - config_autonestbox.ival(0) = 0; - out << "Autonestbox stopped." << endl; - return CR_OK; - } - else if(p == "verbose") - { - verbose = true; - } - else if(p == "sleep") - { - if(i == parameters.size()-1) - { - out.printerr("No duration specified!\n"); - return CR_WRONG_USAGE; - } - else - { - size_t ticks = 0; - stringstream ss(parameters[i+1]); - i++; - ss >> ticks; - if(ticks <= 0) - { - out.printerr("Invalid duration specified (must be > 0)!\n"); - return CR_WRONG_USAGE; - } - sleep_autonestbox = ticks; - if(config_autonestbox.isValid()) - config_autonestbox.ival(1) = sleep_autonestbox; - out << "New sleep timer for autonestbox: " << ticks << " ticks." << endl; - return CR_OK; - } - } - else - { - out << "Unknown command: " << p << endl; - return CR_WRONG_USAGE; - } - } - return autoNestbox(out, verbose); -} - -command_result autoNestbox( color_ostream &out, bool verbose = false ) -{ - bool stop = false; - size_t processed = 0; - - if (!Maps::IsValid()) - { - out.printerr("Map is not available!\n"); - enable_autonestbox = false; - return CR_FAILURE; - } - - do - { - df::building * free_building = findFreeNestboxZone(); - df::unit * free_unit = findFreeEgglayer(); - if(free_building && free_unit) - { - command_result result = assignUnitToBuilding(out, free_unit, free_building, verbose); - if(result != CR_OK) - return result; - processed ++; - } - else - { - stop = true; - if(free_unit && !free_building) - { - static size_t old_count = 0; - size_t freeEgglayers = countFreeEgglayers(); - // avoid spamming the same message - if(old_count != freeEgglayers) - autonestbox_did_complain = false; - old_count = freeEgglayers; - if(!autonestbox_did_complain) - { - stringstream ss; - ss << freeEgglayers; - string announce = "Not enough free nestbox zones found! You need " + ss.str() + " more."; - Gui::showAnnouncement(announce, 6, true); - out << announce << endl; - autonestbox_did_complain = true; - } - } - } - } while (!stop); - if(processed > 0) - { - stringstream ss; - ss << processed; - string announce; - announce = ss.str() + " nestboxes were assigned."; - Gui::showAnnouncement(announce, 2, false); - out << announce << endl; - // can complain again - // (might lead to spamming the same message twice, but catches the case - // where for example 2 new egglayers hatched right after 2 zones were created and assigned) - autonestbox_did_complain = false; - } - return CR_OK; -} - //////////////////// // autobutcher stuff @@ -3129,7 +2962,7 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) } //////////////////////////////////////////////////// -// autobutcher and autonestbox start/init/cleanup +// autobutcher start/init/cleanup command_result start_autobutcher(color_ostream &out) { @@ -3227,62 +3060,6 @@ command_result cleanup_autobutcher(color_ostream &out) return CR_OK; } -command_result start_autonestbox(color_ostream &out) -{ - plugin_enable(out, true); - enable_autonestbox = true; - - if (!config_autonestbox.isValid()) - { - config_autonestbox = World::AddPersistentData("autonestbox/config"); - - if (!config_autonestbox.isValid()) - { - out << "Cannot enable autonestbox without a world!" << endl; - return CR_OK; - } - - config_autonestbox.ival(1) = sleep_autonestbox; - } - - config_autonestbox.ival(0) = enable_autonestbox; - - out << "Starting autonestbox." << endl; - init_autonestbox(out); - return CR_OK; -} - -command_result init_autonestbox(color_ostream &out) -{ - cleanup_autonestbox(out); - - config_autonestbox = World::GetPersistentData("autonestbox/config"); - if(config_autonestbox.isValid()) - { - if (config_autonestbox.ival(0) == -1) - { - config_autonestbox.ival(0) = enable_autonestbox; - config_autonestbox.ival(1) = sleep_autonestbox; - out << "Autonestbox's persistent config object was invalid!" << endl; - } - else - { - enable_autonestbox = config_autonestbox.ival(0); - sleep_autonestbox = config_autonestbox.ival(1); - } - } - if (enable_autonestbox) - plugin_enable(out, true); - return CR_OK; -} - -command_result cleanup_autonestbox(color_ostream &out) -{ - // nothing to cleanup currently - // (future version of autonestbox could store info about cages for useless male kids) - return CR_OK; -} - // abuse WatchedRace struct for counting stocks (since it sorts by gender and age) // calling method must delete pointer! WatchedRace * checkRaceStocksTotal(int race) @@ -4121,24 +3898,17 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Tue, 2 Aug 2022 01:07:13 -0700 Subject: [PATCH 419/854] split autobutcher out from zone --- plugins/CMakeLists.txt | 3 +- plugins/autobutcher.cpp | 1166 +++++++++++++++++++++ plugins/lua/autobutcher.lua | 89 ++ plugins/lua/zone.lua | 12 - plugins/zone.cpp | 1965 ++++------------------------------- 5 files changed, 1437 insertions(+), 1798 deletions(-) create mode 100644 plugins/autobutcher.cpp create mode 100644 plugins/lua/autobutcher.lua delete mode 100644 plugins/lua/zone.lua diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6ef5b59db..338db4ab9 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -83,6 +83,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(3dveins 3dveins.cpp) dfhack_plugin(add-spatter add-spatter.cpp) # dfhack_plugin(advtools advtools.cpp) + dfhack_plugin(autobutcher autobutcher.cpp LINK_LIBRARIES lua) dfhack_plugin(autochop autochop.cpp) dfhack_plugin(autoclothing autoclothing.cpp) dfhack_plugin(autodump autodump.cpp) @@ -178,7 +179,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(workflow workflow.cpp LINK_LIBRARIES lua) dfhack_plugin(workNow workNow.cpp) dfhack_plugin(xlsxreader xlsxreader.cpp LINK_LIBRARIES lua xlsxio_read_STATIC zip expat) - dfhack_plugin(zone zone.cpp LINK_LIBRARIES lua) + dfhack_plugin(zone zone.cpp) # If you are adding a plugin that you do not intend to commit to the DFHack repo, # see instructions for adding "external" plugins at the end of this file. diff --git a/plugins/autobutcher.cpp b/plugins/autobutcher.cpp new file mode 100644 index 000000000..0d7a9c632 --- /dev/null +++ b/plugins/autobutcher.cpp @@ -0,0 +1,1166 @@ +// - full automation of marking live-stock for slaughtering +// races can be added to a watchlist and it can be set how many male/female kids/adults are left alive +// adding to the watchlist can be automated as well. +// config for autobutcher (state and sleep setting) is saved the first time autobutcher is started +// config for watchlist entries is saved when they are created or modified + +#include +#include + +#include "df/building_cagest.h" +#include "df/creature_raw.h" +#include "df/world.h" + +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" + +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/Persistence.h" +#include "modules/Units.h" +#include "modules/World.h" + +using std::string; +using std::unordered_map; +using std::unordered_set; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("autobutcher"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); + +const string autobutcher_help = + "Assigns your lifestock for slaughter once it reaches a specific count. Requires\n" + "that you add the target race(s) to a watch list. Only tame units will be\n" + "processed. Named units will be completely ignored (you can give animals\n" + "nicknames with the tool 'rename unit' to protect them from getting slaughtered\n" + "automatically. Trained war or hunting pets will be ignored.\n" + "Once you have too much adults, the oldest will be butchered first.\n" + "Once you have too much kids, the youngest will be butchered first.\n" + "If you don't set a target count the following default will be used:\n" + "1 male kid, 5 female kids, 1 male adult, 5 female adults.\n" + "Options:\n" + " start - run every X frames (df simulation ticks)\n" + " default: X=6000 (~60 seconds at 100fps)\n" + " stop - stop running automatically\n" + " sleep X - change timer to sleep X frames between runs.\n" + " watch R - start watching race(s)\n" + " R = valid race RAW id (ALPACA, BIRD_TURKEY, etc)\n" + " or a list of RAW ids seperated by spaces\n" + " or the keyword 'all' which affects your whole current watchlist.\n" + " unwatch R - stop watching race(s)\n" + " the current target settings will be remembered\n" + " forget R - unwatch race(s) and forget target settings for it/them\n" + " autowatch - automatically adds all new races (animals you buy\n" + " from merchants, tame yourself or get from migrants)\n" + " to the watch list using default target count\n" + " noautowatch - stop auto-adding new races to the watch list\n" + " list - print status and watchlist\n" + " list_export - print status and watchlist in batchfile format\n" + " can be used to copy settings into another savegame\n" + " usage: 'dfhack-run autobutcher list_export > xyz.bat' \n" + " target fk mk fa ma R\n" + " - set target count for specified race:\n" + " fk = number of female kids\n" + " mk = number of male kids\n" + " fa = number of female adults\n" + " ma = number of female adults\n" + " R = 'all' sets count for all races on the current watchlist\n" + " including the races which are currenly set to 'unwatched'\n" + " and sets the new default for future watch commands\n" + " R = 'new' sets the new default for future watch commands\n" + " without changing your current watchlist\n" + " example - print some usage examples\n"; + +const string autobutcher_help_example = + "Examples:\n" + " autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY\n" + " autobutcher watch ALPACA BIRD_TURKEY\n" + " autobutcher start\n" + " This means you want to have max 7 kids (4 female, 3 male) and max 3 adults\n" + " (2 female, 1 male) of the races alpaca and turkey. Once the kids grow up the\n" + " oldest adults will get slaughtered. Excess kids will get slaughtered starting\n" + " the the youngest to allow that the older ones grow into adults.\n" + " autobutcher target 0 0 0 0 new\n" + " autobutcher autowatch\n" + " autobutcher start\n" + " This tells autobutcher to automatically put all new races onto the watchlist\n" + " and mark unnamed tame units for slaughter as soon as they arrive in your\n" + " fortress. Settings already made for some races will be left untouched.\n"; + +namespace DFHack { + DBG_DECLARE(autobutcher, status); + DBG_DECLARE(autobutcher, cycle); +} + +static const string CONFIG_KEY = "autobutcher/config"; +static const string WATCHLIST_CONFIG_KEY_PREFIX = "autobutcher/watchlist/"; + +static PersistentDataItem config; +enum ConfigValues { + CONFIG_IS_ENABLED = 0, + CONFIG_CYCLE_TICKS = 1, + CONFIG_AUTOWATCH = 2, + CONFIG_DEFAULT_FK = 3, + CONFIG_DEFAULT_MK = 4, + CONFIG_DEFAULT_FA = 5, + CONFIG_DEFAULT_MA = 6, +}; +static int get_config_val(int index) { + if (!config.isValid()) + return -1; + return config.ival(index); +} +static bool get_config_bool(int index) { + return get_config_val(index) == 1; +} +static void set_config_val(int index, int value) { + if (config.isValid()) + config.ival(index) = value; +} +static void set_config_bool(int index, bool value) { + set_config_val(index, value ? 1 : 0); +} + +static unordered_map race_to_id; +static size_t cycle_counter = 0; // how many ticks since the last cycle + +static size_t DEFAULT_CYCLE_TICKS = 6000; + +struct autobutcher_options { + // whether to display help + bool help = false; + + // the command to run. + string command; + + // the set of (unverified) races that the command should affect, and whether + // "all" or "new" was specified as the race + vector races; + bool races_all = false; + bool races_new = false; + + // params for the "target" command + int32_t fk = -1; + int32_t mk = -1; + int32_t fa = -1; + int32_t ma = -1; + + // how many ticks to wait between automatic cycles, -1 means unset + int32_t ticks = -1; + + static struct_identity _identity; + + // non-virtual destructor so offsetof() still works for the fields + ~autobutcher_options() { + for (auto str : races) + delete str; + } +}; +static const struct_field_info autobutcher_options_fields[] = { + { struct_field_info::PRIMITIVE, "help", offsetof(autobutcher_options, help), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "command", offsetof(autobutcher_options, command), df::identity_traits::get(), 0, 0 }, + { struct_field_info::STL_VECTOR_PTR, "races", offsetof(autobutcher_options, races), df::identity_traits::get(), 0, 0 }, + { struct_field_info::PRIMITIVE, "races_all", offsetof(autobutcher_options, races_all), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "races_new", offsetof(autobutcher_options, races_new), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "fk", offsetof(autobutcher_options, fk), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "mk", offsetof(autobutcher_options, mk), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "fa", offsetof(autobutcher_options, fa), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ma", offsetof(autobutcher_options, ma), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ticks", offsetof(autobutcher_options, ticks), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::END } +}; +struct_identity autobutcher_options::_identity(sizeof(autobutcher_options), &df::allocator_fn, NULL, "autobutcher_options", NULL, autobutcher_options_fields); + +static void init_autobutcher(color_ostream &out); +static void cleanup_autobutcher(color_ostream &out); +static command_result df_autobutcher(color_ostream &out, vector ¶meters); +static void autobutcher_cycle(color_ostream &out); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand( + "autobutcher", + "Automatically butcher excess livestock.", + df_autobutcher, + false, + autobutcher_help.c_str())); + + init_autobutcher(out); + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { + if (!Maps::IsValid()) { + out.printerr("Cannot run autobutcher without a loaded map.\n"); + return CR_FAILURE; + } + + if (enable != is_enabled) { + is_enabled = enable; + if (is_enabled) + init_autobutcher(out); + else + cleanup_autobutcher(out); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + cleanup_autobutcher(out); + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + switch (event) { + case DFHack::SC_MAP_LOADED: + init_autobutcher(out); + break; + case DFHack::SC_MAP_UNLOADED: + cleanup_autobutcher(out); + break; + default: + break; + } + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (is_enabled && ++cycle_counter >= (size_t)get_config_val(CONFIG_CYCLE_TICKS)) + autobutcher_cycle(out); + return CR_OK; +} + +///////////////////////////////////////////////////// +// autobutcher config logic +// + +static void doMarkForSlaughter(df::unit *unit) { + unit->flags2.bits.slaughter = 1; +} + +// getUnitAge() returns 0 if born in current year, therefore the look at birth_time in that case +// (assuming that the value from there indicates in which tick of the current year the unit was born) +static bool compareUnitAgesYounger(df::unit *i, df::unit *j) { + int32_t age_i = (int32_t)Units::getAge(i, true); + int32_t age_j = (int32_t)Units::getAge(j, true); + if (age_i == 0 && age_j == 0) { + age_i = i->birth_time; + age_j = j->birth_time; + } + return age_i < age_j; +} + +static bool compareUnitAgesOlder(df::unit* i, df::unit* j) { + int32_t age_i = (int32_t)Units::getAge(i, true); + int32_t age_j = (int32_t)Units::getAge(j, true); + if(age_i == 0 && age_j == 0) { + age_i = i->birth_time; + age_j = j->birth_time; + } + return age_i > age_j; +} + +enum unit_ptr_index { + fk_index = 0, + mk_index = 1, + fa_index = 2, + ma_index = 3 +}; + +struct WatchedRace { +public: + PersistentDataItem rconfig; + + int raceId; + bool isWatched; // if true, autobutcher will process this race + + // target amounts + unsigned fk; // max female kids + unsigned mk; // max male kids + unsigned fa; // max female adults + unsigned ma; // max male adults + + // amounts of protected (not butcherable) units + unsigned fk_prot; + unsigned fa_prot; + unsigned mk_prot; + unsigned ma_prot; + + // butcherable units + vector unit_ptr[4]; + + // priority butcherable units + vector prot_ptr[4]; + + WatchedRace(color_ostream &out, int id, bool watch, unsigned _fk, unsigned _mk, unsigned _fa, unsigned _ma) { + raceId = id; + isWatched = watch; + fk = _fk; + mk = _mk; + fa = _fa; + ma = _ma; + fk_prot = fa_prot = mk_prot = ma_prot = 0; + + DEBUG(status,out).print("creating new WatchedRace: id=%d, watched=%s, fk=%u, mk=%u, fa=%u, ma=%u\n", + id, watch ? "true" : "false", fk, mk, fa, ma); + } + + WatchedRace(color_ostream &out, const PersistentDataItem &p) + : WatchedRace(out, p.ival(0), p.ival(1), p.ival(2), p.ival(3), p.ival(4), p.ival(5)) { + rconfig = p; + } + + ~WatchedRace() { + ClearUnits(); + } + + void UpdateConfig(color_ostream &out) { + if(!rconfig.isValid()) { + string keyname = WATCHLIST_CONFIG_KEY_PREFIX + Units::getRaceNameById(raceId); + rconfig = World::GetPersistentData(keyname, NULL); + } + if(rconfig.isValid()) { + rconfig.ival(0) = raceId; + rconfig.ival(1) = isWatched; + rconfig.ival(2) = fk; + rconfig.ival(3) = mk; + rconfig.ival(4) = fa; + rconfig.ival(5) = ma; + } + else { + ERR(status,out).print("could not create persistent key for race: %s", + Units::getRaceNameById(raceId).c_str()); + } + } + + void RemoveConfig(color_ostream &out) { + if(!rconfig.isValid()) + return; + World::DeletePersistentData(rconfig); + } + + void SortUnitsByAge() { + sort(unit_ptr[fk_index].begin(), unit_ptr[fk_index].end(), compareUnitAgesOlder); + sort(unit_ptr[mk_index].begin(), unit_ptr[mk_index].end(), compareUnitAgesOlder); + sort(unit_ptr[fa_index].begin(), unit_ptr[fa_index].end(), compareUnitAgesYounger); + sort(unit_ptr[ma_index].begin(), unit_ptr[ma_index].end(), compareUnitAgesYounger); + sort(prot_ptr[fk_index].begin(), prot_ptr[fk_index].end(), compareUnitAgesOlder); + sort(prot_ptr[mk_index].begin(), prot_ptr[mk_index].end(), compareUnitAgesOlder); + sort(prot_ptr[fa_index].begin(), prot_ptr[fa_index].end(), compareUnitAgesYounger); + sort(prot_ptr[ma_index].begin(), prot_ptr[ma_index].end(), compareUnitAgesYounger); + } + + void PushUnit(df::unit *unit) { + if(Units::isFemale(unit)) { + if(Units::isBaby(unit) || Units::isChild(unit)) + unit_ptr[fk_index].push_back(unit); + else + unit_ptr[fa_index].push_back(unit); + } + else //treat sex n/a like it was male + { + if(Units::isBaby(unit) || Units::isChild(unit)) + unit_ptr[mk_index].push_back(unit); + else + unit_ptr[ma_index].push_back(unit); + } + } + + void PushPriorityUnit(df::unit *unit) { + if(Units::isFemale(unit)) { + if(Units::isBaby(unit) || Units::isChild(unit)) + prot_ptr[fk_index].push_back(unit); + else + prot_ptr[fa_index].push_back(unit); + } + else { + if(Units::isBaby(unit) || Units::isChild(unit)) + prot_ptr[mk_index].push_back(unit); + else + prot_ptr[ma_index].push_back(unit); + } + } + + void PushProtectedUnit(df::unit *unit) { + if(Units::isFemale(unit)) { + if(Units::isBaby(unit) || Units::isChild(unit)) + fk_prot++; + else + fa_prot++; + } + else { //treat sex n/a like it was male + if(Units::isBaby(unit) || Units::isChild(unit)) + mk_prot++; + else + ma_prot++; + } + } + + void ClearUnits() { + fk_prot = fa_prot = mk_prot = ma_prot = 0; + for (size_t i = 0; i < 4; i++) { + unit_ptr[i].clear(); + prot_ptr[i].clear(); + } + } + + int ProcessUnits(vector& unit_ptr, vector& unit_pri_ptr, unsigned prot, unsigned goal) { + int subcount = 0; + while (unit_pri_ptr.size() && (unit_ptr.size() + unit_pri_ptr.size() + prot > goal)) { + df::unit *unit = unit_pri_ptr.back(); + doMarkForSlaughter(unit); + unit_pri_ptr.pop_back(); + subcount++; + } + while (unit_ptr.size() && (unit_ptr.size() + prot > goal)) { + df::unit *unit = unit_ptr.back(); + doMarkForSlaughter(unit); + unit_ptr.pop_back(); + subcount++; + } + return subcount; + } + + int ProcessUnits() { + SortUnitsByAge(); + int slaughter_count = 0; + slaughter_count += ProcessUnits(unit_ptr[fk_index], prot_ptr[fk_index], fk_prot, fk); + slaughter_count += ProcessUnits(unit_ptr[mk_index], prot_ptr[mk_index], mk_prot, mk); + slaughter_count += ProcessUnits(unit_ptr[fa_index], prot_ptr[fa_index], fa_prot, fa); + slaughter_count += ProcessUnits(unit_ptr[ma_index], prot_ptr[ma_index], ma_prot, ma); + ClearUnits(); + return slaughter_count; + } +}; + +// vector of races handled by autobutcher +// the name is a bit misleading since entries can be set to 'unwatched' +// to ignore them for a while but still keep the target count settings +static unordered_map watched_races; + +static void init_autobutcher(color_ostream &out) { + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) + config = World::AddPersistentData(CONFIG_KEY); + + if (get_config_val(CONFIG_IS_ENABLED) == -1) { + set_config_bool(CONFIG_IS_ENABLED, false); + set_config_val(CONFIG_CYCLE_TICKS, DEFAULT_CYCLE_TICKS); + set_config_bool(CONFIG_AUTOWATCH, false); + set_config_val(CONFIG_DEFAULT_FK, 5); + set_config_val(CONFIG_DEFAULT_MK, 1); + set_config_val(CONFIG_DEFAULT_FA, 5); + set_config_val(CONFIG_DEFAULT_MA, 1); + } + + if (is_enabled) + set_config_bool(CONFIG_IS_ENABLED, true); + else + is_enabled = (get_config_val(CONFIG_IS_ENABLED) == 1); + + if (!config.isValid()) + return; + + if (!race_to_id.size()) { + const size_t num_races = world->raws.creatures.all.size(); + for(size_t i = 0; i < num_races; ++i) + race_to_id.emplace(Units::getRaceNameById(i), i); + } + + std::vector watchlist; + World::GetPersistentData(&watchlist, WATCHLIST_CONFIG_KEY_PREFIX, true); + for (auto & p : watchlist) { + DEBUG(status,out).print("Reading from save: %s\n", p.key().c_str()); + WatchedRace *w = new WatchedRace(out, p); + watched_races.emplace(w->raceId, w); + } +} + +static void cleanup_autobutcher(color_ostream &out) { + is_enabled = false; + race_to_id.clear(); + for (auto w : watched_races) + delete w.second; + watched_races.clear(); +} + +static bool get_options(color_ostream &out, + autobutcher_options &opts, + const vector ¶meters) +{ + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, parameters.size() + 2) || + !Lua::PushModulePublic( + out, L, "plugins.autobutcher", "parse_commandline")) { + out.printerr("Failed to load autobutcher Lua code\n"); + return false; + } + + Lua::Push(L, &opts); + for (const string ¶m : parameters) + Lua::Push(L, param); + + if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) + return false; + + return true; +} + +static void autobutcher_export(color_ostream &out); +static void autobutcher_status(color_ostream &out); +static void autobutcher_target(color_ostream &out, const autobutcher_options &opts); +static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_options &opts); + +static command_result df_autobutcher(color_ostream &out, vector ¶meters) { + CoreSuspender suspend; + + if (!Maps::IsValid()) { + out.printerr("Cannot run autobutcher without a loaded map.\n"); + return CR_FAILURE; + } + + autobutcher_options opts; + if (!get_options(out, opts, parameters) || opts.help) + return CR_WRONG_USAGE; + + if (opts.command == "now") { + autobutcher_cycle(out); + } + else if (opts.command == "autowatch") { + set_config_bool(CONFIG_AUTOWATCH, true); + } + else if (opts.command == "noautowatch") { + set_config_bool(CONFIG_AUTOWATCH, false); + } + else if (opts.command == "list_export") { + autobutcher_export(out); + } + else if (opts.command == "target") { + autobutcher_target(out, opts); + } + else if (opts.command == "watch" || + opts.command == "unwatch" || + opts.command == "forget") { + autobutcher_modify_watchlist(out, opts); + } + else if (opts.command == "ticks") { + set_config_val(CONFIG_CYCLE_TICKS, opts.ticks); + INFO(status,out).print("New cycle timer: %d ticks.\n", opts.ticks); + } + else { + autobutcher_status(out); + } + + return CR_OK; +} + +// helper for sorting the watchlist alphabetically +static bool compareRaceNames(WatchedRace* i, WatchedRace* j) { + string name_i = Units::getRaceNamePluralById(i->raceId); + string name_j = Units::getRaceNamePluralById(j->raceId); + + return name_i < name_j; +} + +// sort watchlist alphabetically +static vector getSortedWatchList() { + vector list; + for (auto w : watched_races) { + list.push_back(w.second); + } + sort(list.begin(), list.end(), compareRaceNames); + return list; +} + +static void autobutcher_export(color_ostream &out) { + out << "enable autobutcher" << endl; + out << "autobutcher ticks " << get_config_val(CONFIG_CYCLE_TICKS) << endl; + out << "autobutcher " << (get_config_bool(CONFIG_AUTOWATCH) ? "" : "no") + << "autowatch" << endl; + out << "autobutcher target" + << " " << get_config_val(CONFIG_DEFAULT_FK) + << " " << get_config_val(CONFIG_DEFAULT_MK) + << " " << get_config_val(CONFIG_DEFAULT_FA) + << " " << get_config_val(CONFIG_DEFAULT_MA) + << " new" << endl; + + for (auto w : getSortedWatchList()) { + df::creature_raw *raw = world->raws.creatures.all[w->raceId]; + string name = raw->creature_id; + out << "autobutcher target" + << " " << w->fk + << " " << w->mk + << " " << w->fa + << " " << w->ma + << " " << name << endl; + if (w->isWatched) + out << "autobutcher watch " << name << endl; + } +} + +static void autobutcher_status(color_ostream &out) { + out << "autobutcher is " << (is_enabled ? "" : "not ") << "enabled\n"; + if (is_enabled) + out << " running every " << get_config_val(CONFIG_CYCLE_TICKS) << " game ticks\n"; + out << " " << (get_config_bool(CONFIG_AUTOWATCH) ? "" : "not ") << "autowatching for new races\n"; + + out << "\ndefault setting for new races:" + << " fk=" << get_config_val(CONFIG_DEFAULT_FK) + << " mk=" << get_config_val(CONFIG_DEFAULT_MK) + << " fa=" << get_config_val(CONFIG_DEFAULT_FA) + << " ma=" << get_config_val(CONFIG_DEFAULT_MA) + << endl << endl; + + if (!watched_races.size()) { + out << "not currently watching any races. to find out how to add some, run:\n help autobutcher" << endl; + return; + } + + out << "monitoring races: " << endl; + for (auto w : getSortedWatchList()) { + df::creature_raw *raw = world->raws.creatures.all[w->raceId]; + out << " " << Units::getRaceNamePluralById(w->raceId) << " \t"; + out << "(" << raw->creature_id; + out << " fk=" << w->fk + << " mk=" << w->mk + << " fa=" << w->fa + << " ma=" << w->ma; + if (!w->isWatched) + out << "; autobutchering is paused"; + out << ")" << endl; + } +} + +static void autobutcher_target(color_ostream &out, const autobutcher_options &opts) { + if (opts.races_new) { + DEBUG(status,out).print("setting targets for new races\n"); + set_config_val(CONFIG_DEFAULT_FK, opts.fk); + set_config_val(CONFIG_DEFAULT_MK, opts.mk); + set_config_val(CONFIG_DEFAULT_FA, opts.fa); + set_config_val(CONFIG_DEFAULT_MA, opts.ma); + } + + if (opts.races_all) { + DEBUG(status,out).print("setting targets for all races on watchlist\n"); + for (auto w : watched_races) { + w.second->fk = opts.fk; + w.second->mk = opts.mk; + w.second->fa = opts.fa; + w.second->ma = opts.ma; + w.second->UpdateConfig(out); + } + } + + for (auto race : opts.races) { + if (!race_to_id.count(*race)) { + out.printerr("race not found: '%s'", race->c_str()); + continue; + } + int id = race_to_id[*race]; + WatchedRace *w; + if (!watched_races.count(id)) { + DEBUG(status,out).print("adding new targets for %s\n", race->c_str()); + w = new WatchedRace(out, id, true, opts.fk, opts.mk, opts.fa, opts.ma); + watched_races.emplace(id, w); + } else { + w = watched_races[id]; + w->fk = opts.fk; + w->mk = opts.mk; + w->fa = opts.fa; + w->ma = opts.ma; + } + w->UpdateConfig(out); + } +} + +static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_options &opts) { + unordered_set ids; + + if (opts.races_all) { + DEBUG(status,out).print("modifying all races on watchlist: %s\n", + opts.command.c_str()); + for (auto w : watched_races) + ids.emplace(w.first); + } + + for (auto race : opts.races) { + if (!race_to_id.count(*race)) { + out.printerr("race not found: '%s'", race->c_str()); + continue; + } + ids.emplace(race_to_id[*race]); + } + + for (int id : ids) { + if (opts.command == "watch") + watched_races[id]->isWatched = true; + else if (opts.command == "unwatch") + watched_races[id]->isWatched = false; + else if (opts.command == "forget") { + watched_races[id]->RemoveConfig(out); + watched_races.erase(id); + continue; + } + watched_races[id]->UpdateConfig(out); + } +} + +///////////////////////////////////////////////////// +// autobutcher cycle logic +// + +// check if contained in item (e.g. animals in cages) +static bool isContainedInItem(df::unit *unit) { + for (auto gref : unit->general_refs) { + if (gref->getType() == df::general_ref_type::CONTAINED_IN_ITEM) { + return true; + } + } + return false; +} + +// found a unit with weird position values on one of my maps (negative and in the thousands) +// it didn't appear in the animal stocks screen, but looked completely fine otherwise (alive, tame, own, etc) +// maybe a rare bug, but better avoid assigning such units to zones or slaughter etc. +static bool hasValidMapPos(df::unit *unit) { + return unit->pos.x >= 0 && unit->pos.y >= 0 && unit->pos.z >= 0 + && unit->pos.x < world->map.x_count + && unit->pos.y < world->map.y_count + && unit->pos.z < world->map.z_count; +} + +// built cage defined as room (supposed to detect zoo cages) +static bool isInBuiltCageRoom(df::unit *unit) { + for (auto building : world->buildings.all) { + // !!! building->isRoom() returns true if the building can be made a room but currently isn't + // !!! except for coffins/tombs which always return false + // !!! using the bool is_room however gives the correct state/value + if (!building->is_room || building->getType() != df::building_type::Cage) + continue; + + df::building_cagest* cage = (df::building_cagest*)building; + for (auto cu : cage->assigned_units) + if (cu == unit->id) return true; + } + return false; +} + +static void autobutcher_cycle(color_ostream &out) { + DEBUG(cycle,out).print("running autobutcher_cycle\n"); + + // check if there is anything to watch before walking through units vector + if (!get_config_bool(CONFIG_AUTOWATCH)) { + bool watching = false; + for (auto w : watched_races) { + if (w.second->isWatched) { + watching = true; + break; + } + } + if (!watching) + return; + } + + for (auto unit : world->units.all) { + // this check is now divided into two steps, squeezed autowatch into the middle + // first one ignores completely inappropriate units (dead, undead, not belonging to the fort, ...) + // then let autowatch add units to the watchlist which will probably start breeding (owned pets, war animals, ...) + // then process units counting those which can't be butchered (war animals, named pets, ...) + // so that they are treated as "own stock" as well and count towards the target quota + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMarkedForSlaughter(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + || !Units::isTame(unit) + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + WatchedRace *w; + if (watched_races.count(unit->race)) { + w = watched_races[unit->race]; + } + else if (!get_config_bool(CONFIG_AUTOWATCH)) { + continue; + } + else { + w = new WatchedRace(out, unit->race, true, get_config_val(CONFIG_DEFAULT_FK), + get_config_val(CONFIG_DEFAULT_MK), get_config_val(CONFIG_DEFAULT_FA), + get_config_val(CONFIG_DEFAULT_MA)); + w->UpdateConfig(out); + watched_races.emplace(unit->race, w); + + string announce = "New race added to autobutcher watchlist: " + Units::getRaceNamePluralById(unit->race); + Gui::showAnnouncement(announce, 2, false); + } + + if (w->isWatched) { + // don't butcher protected units, but count them as stock as well + // this way they count towards target quota, so if you order that you want 1 female adult cat + // and have 2 cats, one of them being a pet, the other gets butchered + if( Units::isWar(unit) // ignore war dogs etc + || Units::isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || Units::isAvailableForAdoption(unit) + || unit->name.has_name) + w->PushProtectedUnit(unit); + else if ( Units::isGay(unit) + || Units::isGelded(unit)) + w->PushPriorityUnit(unit); + else + w->PushUnit(unit); + } + } + + int slaughter_count = 0; + for (auto w : watched_races) { + int slaughter_subcount = w.second->ProcessUnits(); + slaughter_count += slaughter_subcount; + if (slaughter_subcount) { + stringstream ss; + ss << slaughter_subcount; + string announce = Units::getRaceNamePluralById(w.first) + " marked for slaughter: " + ss.str(); + Gui::showAnnouncement(announce, 2, false); + } + } +} + +///////////////////////////////////// +// API functions to control autobutcher with a lua script + +// abuse WatchedRace struct for counting stocks (since it sorts by gender and age) +// calling method must delete pointer! +static WatchedRace * checkRaceStocksTotal(color_ostream &out, int race) { + WatchedRace * w = new WatchedRace(out, race, true, 0, 0, 0, 0); + for (auto unit : world->units.all) { + if (unit->race != race) + continue; + + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + ) + continue; + + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + w->PushUnit(unit); + } + return w; +} + +WatchedRace * checkRaceStocksProtected(color_ostream &out, int race) { + WatchedRace * w = new WatchedRace(out, race, true, 0, 0, 0, 0); + for (auto unit : world->units.all) { + if (unit->race != race) + continue; + + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if (!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + if ( !Units::isTame(unit) + || Units::isWar(unit) // ignore war dogs etc + || Units::isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || Units::isAvailableForAdoption(unit) + || unit->name.has_name ) + w->PushUnit(unit); + } + return w; +} + +WatchedRace * checkRaceStocksButcherable(color_ostream &out, int race) { + WatchedRace * w = new WatchedRace(out, race, true, 0, 0, 0, 0); + for (auto unit : world->units.all) { + if (unit->race != race) + continue; + + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + || !Units::isTame(unit) + || Units::isWar(unit) // ignore war dogs etc + || Units::isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || Units::isAvailableForAdoption(unit) + || unit->name.has_name + ) + continue; + + if (!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + w->PushUnit(unit); + } + return w; +} + +WatchedRace * checkRaceStocksButcherFlag(color_ostream &out, int race) { + WatchedRace * w = new WatchedRace(out, race, true, 0, 0, 0, 0); + for (auto unit : world->units.all) { + if(unit->race != race) + continue; + + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + ) + continue; + + if (!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + if (Units::isMarkedForSlaughter(unit)) + w->PushUnit(unit); + } + return w; +} + +static bool autowatch_isEnabled() { + return get_config_bool(CONFIG_AUTOWATCH); +} + +static unsigned autobutcher_getSleep(color_ostream &out) { + return get_config_val(CONFIG_CYCLE_TICKS); +} + +static void autobutcher_setSleep(color_ostream &out, unsigned ticks) { + + set_config_val(CONFIG_CYCLE_TICKS, ticks); +} + +static void autowatch_setEnabled(color_ostream &out, bool enable) { + DEBUG(status,out).print("auto-adding to watchlist %s\n", enable ? "started" : "stopped"); + set_config_bool(CONFIG_AUTOWATCH, enable); +} + +// set all data for a watchlist race in one go +// if race is not already on watchlist it will be added +// params: (id, fk, mk, fa, ma, watched) +static void autobutcher_setWatchListRace(color_ostream &out, unsigned id, unsigned fk, unsigned mk, unsigned fa, unsigned ma, bool watched) { + if (watched_races.count(id)) { + DEBUG(status,out).print("updating watchlist entry\n"); + WatchedRace * w = watched_races[id]; + w->fk = fk; + w->mk = mk; + w->fa = fa; + w->ma = ma; + w->isWatched = watched; + w->UpdateConfig(out); + return; + } + + DEBUG(status,out).print("creating new watchlist entry\n"); + WatchedRace * w = new WatchedRace(out, id, watched, fk, mk, fa, ma); + w->UpdateConfig(out); + watched_races.emplace(id, w); + + string announce; + announce = "New race added to autobutcher watchlist: " + Units::getRaceNamePluralById(id); + Gui::showAnnouncement(announce, 2, false); +} + +// remove entry from watchlist +static void autobutcher_removeFromWatchList(color_ostream &out, unsigned id) { + if (watched_races.count(id)) { + DEBUG(status,out).print("removing watchlist entry\n"); + WatchedRace * w = watched_races[id]; + w->RemoveConfig(out); + watched_races.erase(id); + } +} + +// set default target values for new races +static void autobutcher_setDefaultTargetNew(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma) { + set_config_val(CONFIG_DEFAULT_FK, fk); + set_config_val(CONFIG_DEFAULT_MK, mk); + set_config_val(CONFIG_DEFAULT_FA, fa); + set_config_val(CONFIG_DEFAULT_MA, ma); +} + +// set default target values for ALL races (update watchlist and set new default) +static void autobutcher_setDefaultTargetAll(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma) { + for (auto w : watched_races) { + w.second->fk = fk; + w.second->mk = mk; + w.second->fa = fa; + w.second->ma = ma; + w.second->UpdateConfig(out); + } + autobutcher_setDefaultTargetNew(out, fk, mk, fa, ma); +} + +static void autobutcher_butcherRace(color_ostream &out, int id) { + for (auto unit : world->units.all) { + if(unit->race != id) + continue; + + if( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draught animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + || !Units::isTame(unit) + || Units::isWar(unit) // ignore war dogs etc + || Units::isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || Units::isAvailableForAdoption(unit) + || unit->name.has_name + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + doMarkForSlaughter(unit); + } +} + +// remove butcher flag for all units of a given race +static void autobutcher_unbutcherRace(color_ostream &out, int id) { + for (auto unit : world->units.all) { + if(unit->race != id) + continue; + + if( !Units::isActive(unit) + || Units::isUndead(unit) + || !Units::isMarkedForSlaughter(unit) + ) + continue; + + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + unit->flags2.bits.slaughter = 0; + } +} + +// push autobutcher settings on lua stack +static int autobutcher_getSettings(lua_State *L) { + lua_newtable(L); + int ctable = lua_gettop(L); + Lua::SetField(L, get_config_bool(CONFIG_IS_ENABLED), ctable, "enable_autobutcher"); + Lua::SetField(L, get_config_bool(CONFIG_AUTOWATCH), ctable, "enable_autowatch"); + Lua::SetField(L, get_config_val(CONFIG_DEFAULT_FK), ctable, "fk"); + Lua::SetField(L, get_config_val(CONFIG_DEFAULT_MK), ctable, "mk"); + Lua::SetField(L, get_config_val(CONFIG_DEFAULT_FA), ctable, "fa"); + Lua::SetField(L, get_config_val(CONFIG_DEFAULT_MA), ctable, "ma"); + Lua::SetField(L, get_config_val(CONFIG_CYCLE_TICKS), ctable, "sleep"); + return 1; +} + +// push the watchlist vector as nested table on the lua stack +static int autobutcher_getWatchList(lua_State *L) { + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + + lua_newtable(L); + int entry_index = 0; + for (auto wr : watched_races) { + lua_newtable(L); + int ctable = lua_gettop(L); + + WatchedRace * w = wr.second; + int id = w->raceId; + Lua::SetField(L, id, ctable, "id"); + Lua::SetField(L, w->isWatched, ctable, "watched"); + Lua::SetField(L, Units::getRaceNamePluralById(id), ctable, "name"); + Lua::SetField(L, w->fk, ctable, "fk"); + Lua::SetField(L, w->mk, ctable, "mk"); + Lua::SetField(L, w->fa, ctable, "fa"); + Lua::SetField(L, w->ma, ctable, "ma"); + + WatchedRace *tally = checkRaceStocksTotal(*out, id); + Lua::SetField(L, tally->unit_ptr[fk_index].size(), ctable, "fk_total"); + Lua::SetField(L, tally->unit_ptr[mk_index].size(), ctable, "mk_total"); + Lua::SetField(L, tally->unit_ptr[fa_index].size(), ctable, "fa_total"); + Lua::SetField(L, tally->unit_ptr[ma_index].size(), ctable, "ma_total"); + delete tally; + + tally = checkRaceStocksProtected(*out, id); + Lua::SetField(L, tally->unit_ptr[fk_index].size(), ctable, "fk_protected"); + Lua::SetField(L, tally->unit_ptr[mk_index].size(), ctable, "mk_protected"); + Lua::SetField(L, tally->unit_ptr[fa_index].size(), ctable, "fa_protected"); + Lua::SetField(L, tally->unit_ptr[ma_index].size(), ctable, "ma_protected"); + delete tally; + + tally = checkRaceStocksButcherable(*out, id); + Lua::SetField(L, tally->unit_ptr[fk_index].size(), ctable, "fk_butcherable"); + Lua::SetField(L, tally->unit_ptr[mk_index].size(), ctable, "mk_butcherable"); + Lua::SetField(L, tally->unit_ptr[fa_index].size(), ctable, "fa_butcherable"); + Lua::SetField(L, tally->unit_ptr[ma_index].size(), ctable, "ma_butcherable"); + delete tally; + + tally = checkRaceStocksButcherFlag(*out, id); + Lua::SetField(L, tally->unit_ptr[fk_index].size(), ctable, "fk_butcherflag"); + Lua::SetField(L, tally->unit_ptr[mk_index].size(), ctable, "mk_butcherflag"); + Lua::SetField(L, tally->unit_ptr[fa_index].size(), ctable, "fa_butcherflag"); + Lua::SetField(L, tally->unit_ptr[ma_index].size(), ctable, "ma_butcherflag"); + delete tally; + + lua_rawseti(L, -2, ++entry_index); + } + + return 1; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(autowatch_isEnabled), + DFHACK_LUA_FUNCTION(autowatch_setEnabled), + DFHACK_LUA_FUNCTION(autobutcher_getSleep), + DFHACK_LUA_FUNCTION(autobutcher_setSleep), + DFHACK_LUA_FUNCTION(autobutcher_setWatchListRace), + DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetNew), + DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetAll), + DFHACK_LUA_FUNCTION(autobutcher_butcherRace), + DFHACK_LUA_FUNCTION(autobutcher_unbutcherRace), + DFHACK_LUA_FUNCTION(autobutcher_removeFromWatchList), + DFHACK_LUA_END +}; + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(autobutcher_getSettings), + DFHACK_LUA_COMMAND(autobutcher_getWatchList), + DFHACK_LUA_END +}; diff --git a/plugins/lua/autobutcher.lua b/plugins/lua/autobutcher.lua new file mode 100644 index 000000000..ab334896c --- /dev/null +++ b/plugins/lua/autobutcher.lua @@ -0,0 +1,89 @@ +local _ENV = mkmodule('plugins.autobutcher') + +--[[ + + Native functions: + + * autobutcher_isEnabled() + * autowatch_isEnabled() + +--]] + +local argparse = require('argparse') + +local function is_int(val) + return val and val == math.floor(val) +end + +local function is_positive_int(val) + return is_int(val) and val > 0 +end + +local function check_nonnegative_int(str) + local val = tonumber(str) + if is_positive_int(val) or val == 0 then return val end + qerror('expecting a non-negative integer, but got: '..tostring(str)) +end + +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 + +local function process_races(opts, races, start_idx) + if #races < start_idx then + qerror('missing list of races (or "all" or "new" keywords)') + end + for i=start_idx,#races do + local race = races[i] + if race == 'all' then + opts.races_all = true + elseif race == 'new' then + opts.races_new = true + else + local str = df.new('string') + str.value = race + opts.races:insert('#', str) + end + end +end + +function parse_commandline(opts, ...) + local positionals = process_args(opts, {...}) + + local command = positionals[1] + if command then opts.command = command end + + if opts.help or not command or command == 'now' or + command == 'autowatch' or command == 'noautowatch' or + command == 'list' or command == 'list_export' then + return + end + + if command == 'watch' or command == 'unwatch' or command == 'forget' then + process_races(opts, positionals, 2) + elseif command == 'target' then + opts.fk = check_nonnegative_int(positionals[2]) + opts.mk = check_nonnegative_int(positionals[3]) + opts.fa = check_nonnegative_int(positionals[4]) + opts.ma = check_nonnegative_int(positionals[5]) + process_races(opts, positionals, 6) + elseif command == 'ticks' then + local ticks = tonumber(positionals[2]) + if not is_positive_int(arg) then + qerror('number of ticks must be a positive integer: ' .. ticks) + else + opts.ticks = ticks + end + else + qerror(('unrecognized command: "%s"'):format(command)) + end +end + +return _ENV diff --git a/plugins/lua/zone.lua b/plugins/lua/zone.lua deleted file mode 100644 index 75c9feec8..000000000 --- a/plugins/lua/zone.lua +++ /dev/null @@ -1,12 +0,0 @@ -local _ENV = mkmodule('plugins.zone') - ---[[ - - Native functions: - - * autobutcher_isEnabled() - * autowatch_isEnabled() - ---]] - -return _ENV diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 6353bfd35..60f1f2b61 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -14,107 +14,59 @@ // - mass-assign creatures using filters // - unassign single creature under cursor from current zone // - pitting own dwarves :) -// - full automation of handling mini-pastures over nestboxes: -// go through all pens, check if they are empty and placed over a nestbox -// find female tame egg-layer who is not assigned to another pen and assign it to nestbox pasture -// maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds -// state and sleep setting is saved the first time autonestbox is started (to avoid writing stuff if the plugin is never used) -// - full automation of marking live-stock for slaughtering -// races can be added to a watchlist and it can be set how many male/female kids/adults are left alive -// adding to the watchlist can be automated as well. -// config for autobutcher (state and sleep setting) is saved the first time autobutcher is started -// config for watchlist entries is saved when they are created or modified - -#include -#include -#include -#include -#include -#include -#include + #include -#include -#include #include -#include -#include -#include - -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include "MiscUtils.h" -#include "uicommon.h" - -#include "LuaTools.h" -#include "DataFuncs.h" - -#include "modules/Units.h" -#include "modules/Maps.h" -#include "modules/Gui.h" -#include "modules/Materials.h" -#include "modules/MapCache.h" -#include "modules/Buildings.h" -#include "modules/World.h" -#include "modules/Screen.h" -#include "MiscUtils.h" -#include +#include +#include -#include "df/ui.h" -#include "df/world.h" -#include "df/world_raws.h" -#include "df/building_def.h" -#include "df/building_civzonest.h" #include "df/building_cagest.h" #include "df/building_chainst.h" -#include "df/building_nest_boxst.h" +#include "df/building_civzonest.h" #include "df/general_ref_building_civzone_assignedst.h" -#include -#include +#include "df/ui.h" +#include "df/unit.h" #include "df/unit_relationship_type.h" -#include "df/unit_soul.h" -#include "df/unit_wound.h" #include "df/viewscreen_dwarfmodest.h" +#include "df/world.h" + +#include "PluginManager.h" +#include "uicommon.h" +#include "VTableInterpose.h" + +#include "modules/Buildings.h" +#include "modules/Gui.h" +#include "modules/Units.h" #include "modules/Translation.h" +using std::function; using std::make_pair; +using std::ostringstream; +using std::pair; +using std::runtime_error; using std::string; using std::unordered_map; using std::unordered_set; using std::vector; using namespace DFHack; -using namespace DFHack::Units; -using namespace DFHack::Buildings; -using namespace df::enums; DFHACK_PLUGIN("zone"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); -REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(cursor); -REQUIRE_GLOBAL(ui); -REQUIRE_GLOBAL(ui_build_selector); REQUIRE_GLOBAL(gps); -REQUIRE_GLOBAL(cur_year); -REQUIRE_GLOBAL(cur_year_tick); - +REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui_building_item_cursor); REQUIRE_GLOBAL(ui_building_assign_type); REQUIRE_GLOBAL(ui_building_assign_is_marked); REQUIRE_GLOBAL(ui_building_assign_units); REQUIRE_GLOBAL(ui_building_assign_items); REQUIRE_GLOBAL(ui_building_in_assign); - REQUIRE_GLOBAL(ui_menu_width); +REQUIRE_GLOBAL(world); -using namespace DFHack::Gui; - -command_result df_zone (color_ostream &out, vector & parameters); -command_result df_autobutcher(color_ostream &out, vector & parameters); - -DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable); +static command_result df_zone (color_ostream &out, vector & parameters); const string zone_help = "Allows easier management of pens/pastures, pits and cages.\n" @@ -200,134 +152,20 @@ const string zone_help_examples = " well, unless you have a mod with egg-laying male elves who give milk...\n"; -const string autobutcher_help = - "Assigns your lifestock for slaughter once it reaches a specific count. Requires\n" - "that you add the target race(s) to a watch list. Only tame units will be\n" - "processed. Named units will be completely ignored (you can give animals\n" - "nicknames with the tool 'rename unit' to protect them from getting slaughtered\n" - "automatically. Trained war or hunting pets will be ignored.\n" - "Once you have too much adults, the oldest will be butchered first.\n" - "Once you have too much kids, the youngest will be butchered first.\n" - "If you don't set a target count the following default will be used:\n" - "1 male kid, 5 female kids, 1 male adult, 5 female adults.\n" - "Options:\n" - " start - run every X frames (df simulation ticks)\n" - " default: X=6000 (~60 seconds at 100fps)\n" - " stop - stop running automatically\n" - " sleep X - change timer to sleep X frames between runs.\n" - " watch R - start watching race(s)\n" - " R = valid race RAW id (ALPACA, BIRD_TURKEY, etc)\n" - " or a list of RAW ids seperated by spaces\n" - " or the keyword 'all' which affects your whole current watchlist.\n" - " unwatch R - stop watching race(s)\n" - " the current target settings will be remembered\n" - " forget R - unwatch race(s) and forget target settings for it/them\n" - " autowatch - automatically adds all new races (animals you buy\n" - " from merchants, tame yourself or get from migrants)\n" - " to the watch list using default target count\n" - " noautowatch - stop auto-adding new races to the watch list\n" - " list - print status and watchlist\n" - " list_export - print status and watchlist in batchfile format\n" - " can be used to copy settings into another savegame\n" - " usage: 'dfhack-run autobutcher list_export > xyz.bat' \n" - " target fk mk fa ma R\n" - " - set target count for specified race:\n" - " fk = number of female kids\n" - " mk = number of male kids\n" - " fa = number of female adults\n" - " ma = number of female adults\n" - " R = 'all' sets count for all races on the current watchlist\n" - " including the races which are currenly set to 'unwatched'\n" - " and sets the new default for future watch commands\n" - " R = 'new' sets the new default for future watch commands\n" - " without changing your current watchlist\n" - " example - print some usage examples\n"; - -const string autobutcher_help_example = - "Examples:\n" - " autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY\n" - " autobutcher watch ALPACA BIRD_TURKEY\n" - " autobutcher start\n" - " This means you want to have max 7 kids (4 female, 3 male) and max 3 adults\n" - " (2 female, 1 male) of the races alpaca and turkey. Once the kids grow up the\n" - " oldest adults will get slaughtered. Excess kids will get slaughtered starting\n" - " the the youngest to allow that the older ones grow into adults.\n" - " autobutcher target 0 0 0 0 new\n" - " autobutcher autowatch\n" - " autobutcher start\n" - " This tells autobutcher to automatically put all new races onto the watchlist\n" - " and mark unnamed tame units for slaughter as soon as they arrive in your\n" - " fortress. Settings already made for some races will be left untouched.\n"; - -command_result init_autobutcher(color_ostream &out); -command_result cleanup_autobutcher(color_ostream &out); -command_result start_autobutcher(color_ostream &out); - - -/////////////// -// stuff for autobutcher -// should be moved to own plugin once the tool methods it shares with the zone plugin are moved to Unit.h / Building.h - -command_result autoNestbox( color_ostream &out, bool verbose ); -command_result autoButcher( color_ostream &out, bool verbose ); - -static bool enable_autobutcher = false; -static bool enable_autobutcher_autowatch = false; -static size_t sleep_autobutcher = 6000; - -static PersistentDataItem config_autobutcher; - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) - { - case DFHack::SC_MAP_LOADED: - // initialize from the world just loaded - init_autobutcher(out); - break; - case DFHack::SC_MAP_UNLOADED: - enable_autobutcher = false; - // cleanup - cleanup_autobutcher(out); - break; - default: - break; - } - return CR_OK; -} - -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) -{ - static size_t ticks_autobutcher = 0; - - if(enable_autobutcher) - { - if(++ticks_autobutcher >= sleep_autobutcher) - { - ticks_autobutcher= 0; - autoButcher(out, false); - } - } - - return CR_OK; -} - - /////////////// // Various small tool functions // probably many of these should be moved to Unit.h and Building.h sometime later... -df::general_ref_building_civzone_assignedst * createCivzoneRef(); -bool unassignUnitFromBuilding(df::unit* unit); -command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose); -void unitInfo(color_ostream & out, df::unit* creature, bool verbose); -void zoneInfo(color_ostream & out, df::building* building, bool verbose); -void cageInfo(color_ostream & out, df::building* building, bool verbose); -void chainInfo(color_ostream & out, df::building* building, bool verbose); -bool isBuiltCageAtPos(df::coord pos); -bool isInBuiltCageRoom(df::unit*); - -void doMarkForSlaughter(df::unit* unit) +// static df::general_ref_building_civzone_assignedst * createCivzoneRef(); +// static bool unassignUnitFromBuilding(df::unit* unit); +// static command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose); +// static void unitInfo(color_ostream & out, df::unit* creature, bool verbose); +// static void zoneInfo(color_ostream & out, df::building* building, bool verbose); +// static void cageInfo(color_ostream & out, df::building* building, bool verbose); +// static void chainInfo(color_ostream & out, df::building* building, bool verbose); +static bool isInBuiltCageRoom(df::unit*); + +static void doMarkForSlaughter(df::unit* unit) { unit->flags2.bits.slaughter = 1; } @@ -335,7 +173,7 @@ void doMarkForSlaughter(df::unit* unit) // found a unit with weird position values on one of my maps (negative and in the thousands) // it didn't appear in the animal stocks screen, but looked completely fine otherwise (alive, tame, own, etc) // maybe a rare bug, but better avoid assigning such units to zones or slaughter etc. -bool hasValidMapPos(df::unit* unit) +static bool hasValidMapPos(df::unit* unit) { if( unit->pos.x >=0 && unit->pos.y >= 0 && unit->pos.z >= 0 && unit->pos.x < world->map.x_count @@ -347,7 +185,7 @@ bool hasValidMapPos(df::unit* unit) } // dump some unit info -void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) +static void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) { out.print("Unit %d ", unit->id); //race %d, civ %d,", creature->race, creature->civ_id if(unit->name.has_name) @@ -364,11 +202,11 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) out << ", "; } - if(isAdult(unit)) + if(Units::isAdult(unit)) out << "adult"; - else if(isBaby(unit)) + else if(Units::isBaby(unit)) out << "baby"; - else if(isChild(unit)) + else if(Units::isChild(unit)) out << "child"; out << " "; // sometimes empty even in vanilla RAWS, sometimes contains full race name (e.g. baby alpaca) @@ -376,7 +214,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) //out << getRaceBabyName(unit); //out << getRaceChildName(unit); - out << getRaceName(unit) << " ("; + out << Units::getRaceName(unit) << " ("; switch(unit->sex) { case 0: @@ -391,26 +229,26 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) break; } out << ")"; - out << ", age: " << getAge(unit, true); + out << ", age: " << Units::getAge(unit, true); - if(isTame(unit)) + if(Units::isTame(unit)) out << ", tame"; - if(isOwnCiv(unit)) + if(Units::isOwnCiv(unit)) out << ", owned"; - if(isWar(unit)) + if(Units::isWar(unit)) out << ", war"; - if(isHunter(unit)) + if(Units::isHunter(unit)) out << ", hunter"; - if(isMerchant(unit)) + if(Units::isMerchant(unit)) out << ", merchant"; - if(isForest(unit)) + if(Units::isForest(unit)) out << ", forest"; - if(isEggLayer(unit)) + if(Units::isEggLayer(unit)) out << ", egglayer"; - if(isGrazer(unit)) + if(Units::isGrazer(unit)) out << ", grazer"; - if(isMilkable(unit)) + if(Units::isMilkable(unit)) out << ", milkable"; if(unit->flags2.bits.slaughter) out << ", slaughter"; @@ -418,7 +256,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) if(verbose) { out << ". Pos: ("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ") " << endl; - out << "index in units vector: " << findIndexById(unit->id) << endl; + out << "index in units vector: " << Units::findIndexById(unit->id) << endl; } out << endl; @@ -461,17 +299,17 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) } } -bool isCage(df::building * building) +static bool isCage(df::building * building) { - return building && (building->getType() == building_type::Cage); + return building && (building->getType() == df::building_type::Cage); } -bool isChain(df::building * building) +static bool isChain(df::building * building) { - return building && (building->getType() == building_type::Chain); + return building && (building->getType() == df::building_type::Chain); } -df::general_ref_building_civzone_assignedst * createCivzoneRef() +static df::general_ref_building_civzone_assignedst * createCivzoneRef() { static bool vt_initialized = false; df::general_ref_building_civzone_assignedst* newref = NULL; @@ -510,14 +348,14 @@ df::general_ref_building_civzone_assignedst * createCivzoneRef() return newref; } -bool isInBuiltCage(df::unit* unit); +static bool isInBuiltCage(df::unit* unit); // check if assigned to pen, pit, (built) cage or chain // note: BUILDING_CAGED is not set for animals (maybe it's used for dwarves who get caged as sentence) // animals in cages (no matter if built or on stockpile) get the ref CONTAINED_IN_ITEM instead // removing them from cages on stockpiles is no problem even without clearing the ref // and usually it will be desired behavior to do so. -bool isAssigned(df::unit* unit) +static bool isAssigned(df::unit* unit) { bool assigned = false; for (size_t r=0; r < unit->general_refs.size(); r++) @@ -537,7 +375,7 @@ bool isAssigned(df::unit* unit) return assigned; } -bool isAssignedToZone(df::unit* unit) +static bool isAssignedToZone(df::unit* unit) { bool assigned = false; for (size_t r=0; r < unit->general_refs.size(); r++) @@ -553,26 +391,8 @@ bool isAssignedToZone(df::unit* unit) return assigned; } -// check if assigned to a chain or built cage -// (need to check if the ref needs to be removed, until then touching them is forbidden) -bool isChained(df::unit* unit) -{ - bool contained = false; - for (size_t r=0; r < unit->general_refs.size(); r++) - { - df::general_ref * ref = unit->general_refs[r]; - auto rtype = ref->getType(); - if(rtype == df::general_ref_type::BUILDING_CHAIN) - { - contained = true; - break; - } - } - return contained; -} - // check if contained in item (e.g. animals in cages) -bool isContainedInItem(df::unit* unit) +static bool isContainedInItem(df::unit* unit) { bool contained = false; for (size_t r=0; r < unit->general_refs.size(); r++) @@ -588,13 +408,13 @@ bool isContainedInItem(df::unit* unit) return contained; } -bool isInBuiltCage(df::unit* unit) +static bool isInBuiltCage(df::unit* unit) { bool caged = false; for (size_t b=0; b < world->buildings.all.size(); b++) { df::building* building = world->buildings.all[b]; - if( building->getType() == building_type::Cage) + if( building->getType() == df::building_type::Cage) { df::building_cagest* cage = (df::building_cagest*) building; for(size_t c=0; cassigned_units.size(); c++) @@ -613,7 +433,7 @@ bool isInBuiltCage(df::unit* unit) } // built cage defined as room (supposed to detect zoo cages) -bool isInBuiltCageRoom(df::unit* unit) +static bool isInBuiltCageRoom(df::unit* unit) { bool caged_room = false; for (size_t b=0; b < world->buildings.all.size(); b++) @@ -626,7 +446,7 @@ bool isInBuiltCageRoom(df::unit* unit) if(!building->is_room) continue; - if(building->getType() == building_type::Cage) + if(building->getType() == df::building_type::Cage) { df::building_cagest* cage = (df::building_cagest*) building; for(size_t c=0; cassigned_units.size(); c++) @@ -644,35 +464,13 @@ bool isInBuiltCageRoom(df::unit* unit) return caged_room; } -// check a map position for a built cage -// animals in cages are CONTAINED_IN_ITEM, no matter if they are on a stockpile or inside a built cage -// if they are on animal stockpiles they should count as unassigned to allow pasturing them -// if they are inside built cages they should be ignored in case the cage is a zoo or linked to a lever or whatever -bool isBuiltCageAtPos(df::coord pos) -{ - bool cage = false; - for (size_t b=0; b < world->buildings.all.size(); b++) - { - df::building* building = world->buildings.all[b]; - if( building->getType() == building_type::Cage - && building->x1 == pos.x - && building->y1 == pos.y - && building->z == pos.z ) - { - cage = true; - break; - } - } - return cage; -} - -df::building * getBuiltCageAtPos(df::coord pos) +static df::building * getBuiltCageAtPos(df::coord pos) { df::building* cage = NULL; for (size_t b=0; b < world->buildings.all.size(); b++) { df::building* building = world->buildings.all[b]; - if( building->getType() == building_type::Cage + if( building->getType() == df::building_type::Cage && building->x1 == pos.x && building->y1 == pos.y && building->z == pos.z ) @@ -688,120 +486,12 @@ df::building * getBuiltCageAtPos(df::coord pos) return cage; } -bool isNestboxAtPos(int32_t x, int32_t y, int32_t z) -{ - bool found = false; - for (size_t b=0; b < world->buildings.all.size(); b++) - { - df::building* building = world->buildings.all[b]; - if( building->getType() == building_type::NestBox - && building->x1 == x - && building->y1 == y - && building->z == z ) - { - found = true; - break; - } - } - return found; -} - -bool isFreeNestboxAtPos(int32_t x, int32_t y, int32_t z) -{ - bool found = false; - for (size_t b=0; b < world->buildings.all.size(); b++) - { - df::building* building = world->buildings.all[b]; - if( building->getType() == building_type::NestBox - && building->x1 == x - && building->y1 == y - && building->z == z ) - { - df::building_nest_boxst* nestbox = (df::building_nest_boxst*) building; - if(nestbox->claimed_by == -1 && nestbox->contained_items.size() == 1) - { - found = true; - break; - } - } - } - return found; -} - -bool isEmptyPasture(df::building* building) -{ - if(!isPenPasture(building)) - return false; - df::building_civzonest * civ = (df::building_civzonest *) building; - if(civ->assigned_units.size() == 0) - return true; - else - return false; -} - -df::building* findFreeNestboxZone() -{ - df::building * free_building = NULL; - for (size_t b=0; b < world->buildings.all.size(); b++) - { - df::building* building = world->buildings.all[b]; - if( isEmptyPasture(building) && - isActive(building) && - isFreeNestboxAtPos(building->x1, building->y1, building->z)) - { - free_building = building; - break; - } - } - return free_building; -} - -bool isFreeEgglayer(df::unit * unit) -{ - return isActive(unit) && !isUndead(unit) - && isFemale(unit) - && isTame(unit) - && isOwnCiv(unit) - && isEggLayer(unit) - && !isAssigned(unit) - && !isGrazer(unit) // exclude grazing birds because they're messy - && !isMerchant(unit) // don't steal merchant mounts - && !isForest(unit); // don't steal birds from traders, they hate that -} - -df::unit * findFreeEgglayer() -{ - df::unit* free_unit = NULL; - for (size_t i=0; i < world->units.all.size(); i++) - { - df::unit* unit = world->units.all[i]; - if(isFreeEgglayer(unit)) - { - free_unit = unit; - break; - } - } - return free_unit; -} - -size_t countFreeEgglayers() -{ - size_t count = 0; - for (size_t i=0; i < world->units.all.size(); i++) - { - df::unit* unit = world->units.all[i]; - if(isFreeEgglayer(unit)) - count ++; - } - return count; -} - // check if unit is already assigned to a zone, remove that ref from unit and old zone // check if unit is already assigned to a cage, remove that ref from the cage // returns false if no cage or pasture information was found // helps as workaround for http://www.bay12games.com/dwarves/mantisbt/view.php?id=4475 by the way // (pastured animals assigned to chains will get hauled back and forth because the pasture ref is not deleted) -bool unassignUnitFromBuilding(df::unit* unit) +static bool unassignUnitFromBuilding(df::unit* unit) { bool success = false; for (std::size_t idx = 0; idx < unit->general_refs.size(); idx++) @@ -885,10 +575,10 @@ bool unassignUnitFromBuilding(df::unit* unit) } // assign to pen or pit -command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false) +static command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false) { // building must be a pen/pasture or pit - if(!isPenPasture(building) && !isPitPond(building)) + if(!Buildings::isPenPasture(building) && !Buildings::isPitPond(building)) { out << "Invalid building type. This is not a pen/pasture or pit/pond." << endl; return CR_WRONG_USAGE; @@ -926,18 +616,18 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building civz->assigned_units.push_back(unit->id); out << "Unit " << unit->id - << "(" << getRaceName(unit) << ")" + << "(" << Units::getRaceName(unit) << ")" << " assigned to zone " << building->id; - if(isPitPond(building)) + if(Buildings::isPitPond(building)) out << " (pit/pond)."; - if(isPenPasture(building)) + if(Buildings::isPenPasture(building)) out << " (pen/pasture)."; out << endl; return CR_OK; } -command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building* building, bool verbose) +static command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building* building, bool verbose) { // building must be a pen/pasture or pit if(!isCage(building)) @@ -967,24 +657,24 @@ command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building civz->assigned_units.push_back(unit->id); out << "Unit " << unit->id - << "(" << getRaceName(unit) << ")" + << "(" << Units::getRaceName(unit) << ")" << " assigned to cage " << building->id; out << endl; return CR_OK; } -command_result assignUnitToChain(color_ostream& out, df::unit* unit, df::building* building, bool verbose) +static command_result assignUnitToChain(color_ostream& out, df::unit* unit, df::building* building, bool verbose) { out << "sorry. assigning to chains is not possible yet." << endl; return CR_WRONG_USAGE; } -command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::building* building, bool verbose) +static command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::building* building, bool verbose) { command_result result = CR_WRONG_USAGE; - if(isActivityZone(building)) + if(Buildings::isActivityZone(building)) result = assignUnitToZone(out, unit, building, verbose); else if(isCage(building)) result = assignUnitToCage(out, unit, building, verbose); @@ -996,9 +686,9 @@ command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::buil return result; } -command_result assignUnitsToCagezone(color_ostream& out, vector units, df::building* building, bool verbose) +static command_result assignUnitsToCagezone(color_ostream& out, vector units, df::building* building, bool verbose) { - if(!isPenPasture(building)) + if(!Buildings::isPenPasture(building)) { out << "A cage zone needs to be a pen/pasture containing at least one cage!" << endl; return CR_WRONG_USAGE; @@ -1055,10 +745,10 @@ command_result assignUnitsToCagezone(color_ostream& out, vector units return CR_OK; } -command_result nickUnitsInZone(color_ostream& out, df::building* building, string nick) +static command_result nickUnitsInZone(color_ostream& out, df::building* building, string nick) { // building must be a pen/pasture or pit - if(!isPenPasture(building) && !isPitPond(building)) + if(!Buildings::isPenPasture(building) && !Buildings::isPitPond(building)) { out << "Invalid building type. This is not a pen/pasture or pit/pond." << endl; return CR_WRONG_USAGE; @@ -1075,7 +765,7 @@ command_result nickUnitsInZone(color_ostream& out, df::building* building, strin return CR_OK; } -command_result nickUnitsInCage(color_ostream& out, df::building* building, string nick) +static command_result nickUnitsInCage(color_ostream& out, df::building* building, string nick) { // building must be a pen/pasture or pit if(!isCage(building)) @@ -1095,7 +785,7 @@ command_result nickUnitsInCage(color_ostream& out, df::building* building, strin return CR_OK; } -command_result nickUnitsInChain(color_ostream& out, df::building* building, string nick) +static command_result nickUnitsInChain(color_ostream& out, df::building* building, string nick) { out << "sorry. nicknaming chained units is not possible yet." << endl; return CR_WRONG_USAGE; @@ -1103,11 +793,11 @@ command_result nickUnitsInChain(color_ostream& out, df::building* building, stri // give all units inside a pasture or cage the same nickname // (usage example: protect them from being autobutchered) -command_result nickUnitsInBuilding(color_ostream& out, df::building* building, string nick) +static command_result nickUnitsInBuilding(color_ostream& out, df::building* building, string nick) { command_result result = CR_WRONG_USAGE; - if(isActivityZone(building)) + if(Buildings::isActivityZone(building)) result = nickUnitsInZone(out, building, nick); else if(isCage(building)) result = nickUnitsInCage(out, building, nick); @@ -1120,9 +810,9 @@ command_result nickUnitsInBuilding(color_ostream& out, df::building* building, s } // dump some zone info -void zoneInfo(color_ostream & out, df::building* building, bool verbose) +static void zoneInfo(color_ostream & out, df::building* building, bool verbose) { - if(!isActivityZone(building)) + if(!Buildings::isActivityZone(building)) return; string name; @@ -1138,7 +828,7 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose) out.print("\n"); df::building_civzonest * civ = (df::building_civzonest *) building; - if(isActive(civ)) + if(Buildings::isActive(civ)) out << "active"; else out << "not active"; @@ -1180,7 +870,7 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose) } // dump some cage info -void cageInfo(color_ostream & out, df::building* building, bool verbose) +static void cageInfo(color_ostream & out, df::building* building, bool verbose) { if(!isCage(building)) return; @@ -1220,7 +910,7 @@ void cageInfo(color_ostream & out, df::building* building, bool verbose) } // dump some chain/restraint info -void chainInfo(color_ostream & out, df::building* building, bool list_refs = false) +static void chainInfo(color_ostream & out, df::building* building, bool list_refs = false) { if(!isChain(building)) return; @@ -1247,7 +937,7 @@ void chainInfo(color_ostream & out, df::building* building, bool list_refs = fal } } -df::building* getAssignableBuildingAtCursor(color_ostream& out) +static df::building* getAssignableBuildingAtCursor(color_ostream& out) { // set building at cursor position to be new target building if (cursor->x == -30000) @@ -1267,7 +957,7 @@ df::building* getAssignableBuildingAtCursor(color_ostream& out) } else { - auto zone_at_tile = findPenPitAt(Gui::getCursorPos()); + auto zone_at_tile = Buildings::findPenPitAt(Gui::getCursorPos()); if(!zone_at_tile) { out << "No pen/pasture, pit, or cage under cursor!" << endl; @@ -1284,38 +974,38 @@ df::building* getAssignableBuildingAtCursor(color_ostream& out) // ZONE FILTERS (as in, filters used by 'zone') // Maps parameter names to filters. -unordered_map> zone_filters; +static unordered_map> zone_filters; static struct zone_filters_init { zone_filters_init() { zone_filters["caged"] = isContainedInItem; - zone_filters["egglayer"] = isEggLayer; - zone_filters["female"] = isFemale; - zone_filters["grazer"] = isGrazer; - zone_filters["hunting"] = isHunter; - zone_filters["male"] = isMale; - zone_filters["milkable"] = isMilkable; - zone_filters["naked"] = isNaked; - zone_filters["own"] = isOwnCiv; - zone_filters["tamable"] = isTamable; - zone_filters["tame"] = isTame; + zone_filters["egglayer"] = Units::isEggLayer; + zone_filters["female"] = Units::isFemale; + zone_filters["grazer"] = Units::isGrazer; + zone_filters["hunting"] = Units::isHunter; + zone_filters["male"] = Units::isMale; + zone_filters["milkable"] = Units::isMilkable; + zone_filters["naked"] = Units::isNaked; + zone_filters["own"] = Units::isOwnCiv; + zone_filters["tamable"] = Units::isTamable; + zone_filters["tame"] = Units::isTame; zone_filters["trainablewar"] = [](df::unit *unit) -> bool { - return !isWar(unit) && !isHunter(unit) && isTrainableWar(unit); + return !Units::isWar(unit) && !Units::isHunter(unit) && Units::isTrainableWar(unit); }; zone_filters["trainablehunt"] = [](df::unit *unit) -> bool { - return !isWar(unit) && !isHunter(unit) && isTrainableHunting(unit); + return !Units::isWar(unit) && !Units::isHunter(unit) && Units::isTrainableHunting(unit); }; - zone_filters["trained"] = isTrained; + zone_filters["trained"] = Units::isTrained; // backwards compatibility zone_filters["unassigned"] = [](df::unit *unit) -> bool { return !isAssigned(unit); }; - zone_filters["war"] = isWar; + zone_filters["war"] = Units::isWar; }} zone_filters_init_; // Extra annotations / descriptions for parameter names. -unordered_map zone_filter_notes; +static unordered_map zone_filter_notes; static struct zone_filter_notes_init { zone_filter_notes_init() { zone_filter_notes["caged"] = "caged (ignores built cages)"; zone_filter_notes["hunting"] = "trained hunting creature"; @@ -1326,7 +1016,7 @@ static struct zone_filter_notes_init { zone_filter_notes_init() { zone_filter_notes["war"] = "trained war creature"; }} zone_filter_notes_init_; -pair> createRaceFilter(vector &filter_args) +static pair> createRaceFilter(vector &filter_args) { // guaranteed to exist. string race = filter_args[0]; @@ -1334,12 +1024,12 @@ pair> createRaceFilter(vector &filter_ return make_pair( "race of " + race, [race](df::unit *unit) -> bool { - return getRaceName(unit) == race; + return Units::getRaceName(unit) == race; } ); } -pair> createAgeFilter(vector &filter_args) +static pair> createAgeFilter(vector &filter_args) { int target_age; stringstream ss(filter_args[0]); @@ -1360,12 +1050,12 @@ pair> createAgeFilter(vector &filter_a return make_pair( "age of exactly " + int_to_string(target_age), [target_age](df::unit *unit) -> bool { - return getAge(unit, true) == target_age; + return Units::getAge(unit, true) == target_age; } ); } -pair> createMinAgeFilter(vector &filter_args) +static pair> createMinAgeFilter(vector &filter_args) { double min_age; stringstream ss(filter_args[0]); @@ -1386,12 +1076,12 @@ pair> createMinAgeFilter(vector &filte return make_pair( "minimum age of " + int_to_string(min_age), [min_age](df::unit *unit) -> bool { - return getAge(unit, true) >= min_age; + return Units::getAge(unit, true) >= min_age; } ); } -pair> createMaxAgeFilter(vector &filter_args) +static pair> createMaxAgeFilter(vector &filter_args) { double max_age; stringstream ss(filter_args[0]); @@ -1412,7 +1102,7 @@ pair> createMaxAgeFilter(vector &filte return make_pair( "maximum age of " + int_to_string(max_age), [max_age](df::unit *unit) -> bool { - return getAge(unit, true) <= max_age; + return Units::getAge(unit, true) <= max_age; } ); } @@ -1428,7 +1118,7 @@ pair> createMaxAgeFilter(vector &filte // Constructor functions are permitted to throw strings, which will be caught and printed. // Result filter functions are not permitted to throw std::exceptions. // Result filter functions should not store references -unordered_map>(vector&)>>> zone_param_filters; static struct zone_param_filters_init { zone_param_filters_init() { zone_param_filters["race"] = make_pair(1, createRaceFilter); @@ -1437,7 +1127,7 @@ static struct zone_param_filters_init { zone_param_filters_init() { zone_param_filters["maxage"] = make_pair(1, createMaxAgeFilter); }} zone_param_filters_init_; -command_result df_zone (color_ostream &out, vector & parameters) +static command_result df_zone (color_ostream &out, vector & parameters) { CoreSuspender suspend; @@ -1526,7 +1216,7 @@ command_result df_zone (color_ostream &out, vector & parameters) else if(p0 == "unassign") { // if there's a unit selected... - df::unit *unit = getSelectedUnit(out, true); + df::unit *unit = Gui::getSelectedUnit(out, true); if (unit) { // remove assignment reference from unit and old zone if(unassignUnitFromBuilding(unit)) @@ -1865,7 +1555,7 @@ command_result df_zone (color_ostream &out, vector & parameters) active_filters.push_back([](df::unit *unit) { - return !isMerchant(unit) && !isForest(unit); + return !Units::isMerchant(unit) && !Units::isForest(unit); } ); } else { @@ -1875,7 +1565,7 @@ command_result df_zone (color_ostream &out, vector & parameters) active_filters.push_back([](df::unit *unit) { - return isMerchant(unit) || isForest(unit); + return Units::isMerchant(unit) || Units::isForest(unit); } ); } @@ -1926,7 +1616,7 @@ command_result df_zone (color_ostream &out, vector & parameters) { // filter for units in the building unordered_set assigned_unit_ids; - if(isActivityZone(target_building)) + if(Buildings::isActivityZone(target_building)) { df::building_civzonest *civz = (df::building_civzonest *) target_building; auto &assigned_units_vec = civz->assigned_units; @@ -1984,7 +1674,7 @@ command_result df_zone (color_ostream &out, vector & parameters) if(!race_filter_set && (building_assign || cagezone_assign || unit_slaughter)) { - string own_race_name = getRaceNameById(ui->race_id); + string own_race_name = Units::getRaceNameById(ui->race_id); out.color(COLOR_BROWN); out << "Default filter for " << parameters[0] << ": 'not (race " << own_race_name << " or own civilization)'; use 'race " @@ -1994,7 +1684,7 @@ command_result df_zone (color_ostream &out, vector & parameters) active_filters.push_back([](df::unit *unit) { - return !isOwnRace(unit) || !isOwnCiv(unit); + return !Units::isOwnRace(unit) || !Units::isOwnCiv(unit); } ); } @@ -2022,7 +1712,7 @@ command_result df_zone (color_ostream &out, vector & parameters) active_filters.push_back([](df::unit *unit) { - return !isMerchant(unit) && !isForest(unit); + return !Units::isMerchant(unit) && !Units::isForest(unit); } ); } @@ -2058,7 +1748,7 @@ command_result df_zone (color_ostream &out, vector & parameters) df::unit *unit = *unit_it; // ignore inactive and undead units - if (!isActive(unit) || isUndead(unit)) { + if (!Units::isActive(unit) || Units::isUndead(unit)) { continue; } @@ -2130,10 +1820,10 @@ command_result df_zone (color_ostream &out, vector & parameters) if (removed) { out << "Unit " << unit->id - << "(" << getRaceName(unit) << ")" + << "(" << Units::getRaceName(unit) << ")" << " unassigned from"; - if (isActivityZone(target_building)) + if (Buildings::isActivityZone(target_building)) { out << " zone "; } @@ -2179,7 +1869,7 @@ command_result df_zone (color_ostream &out, vector & parameters) else { // must have unit selected - df::unit *unit = getSelectedUnit(out, true); + df::unit *unit = Gui::getSelectedUnit(out, true); if (!unit) { out.color(COLOR_RED); out << "Error: no unit selected!" << endl; @@ -2207,1361 +1897,72 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_OK; } -//////////////////// -// autobutcher stuff - -// getUnitAge() returns 0 if born in current year, therefore the look at birth_time in that case -// (assuming that the value from there indicates in which tick of the current year the unit was born) -bool compareUnitAgesYounger(df::unit* i, df::unit* j) -{ - int32_t age_i = (int32_t) getAge(i, true); - int32_t age_j = (int32_t) getAge(j, true); - if(age_i == 0 && age_j == 0) - { - age_i = i->birth_time; - age_j = j->birth_time; - } - return (age_i < age_j); -} -bool compareUnitAgesOlder(df::unit* i, df::unit* j) -{ - int32_t age_i = (int32_t) getAge(i, true); - int32_t age_j = (int32_t) getAge(j, true); - if(age_i == 0 && age_j == 0) - { - age_i = i->birth_time; - age_j = j->birth_time; - } - return (age_i > age_j); -} - - - -//enum WatchedRaceSubtypes -//{ -// femaleKid=0, -// maleKid, -// femaleAdult, -// maleAdult -//}; - -enum unit_ptr_index -{ - fk_index = 0, - mk_index = 1, - fa_index = 2, - ma_index = 3 -}; +//START zone filters -struct WatchedRace +class zone_filter { public: - PersistentDataItem rconfig; - - bool isWatched; // if true, autobutcher will process this race - int raceId; - - // target amounts - unsigned fk; // max female kids - unsigned mk; // max male kids - unsigned fa; // max female adults - unsigned ma; // max male adults - - // amounts of protected (not butcherable) units - unsigned fk_prot; - unsigned fa_prot; - unsigned mk_prot; - unsigned ma_prot; - - // butcherable units - vector unit_ptr[4]; - - // priority butcherable units - vector prot_ptr[4]; - - WatchedRace(bool watch, int id, unsigned _fk, unsigned _mk, unsigned _fa, unsigned _ma) - { - isWatched = watch; - raceId = id; - fk = _fk; - mk = _mk; - fa = _fa; - ma = _ma; - fk_prot = fa_prot = mk_prot = ma_prot = 0; - } - - ~WatchedRace() + zone_filter() { - ClearUnits(); + initialized = false; } - void UpdateConfig(color_ostream & out) + void initialize(const df::ui_sidebar_mode &mode) { - if(!rconfig.isValid()) - { - string keyname = "autobutcher/watchlist/" + getRaceNameById(raceId); - rconfig = World::GetPersistentData(keyname, NULL); - } - if(rconfig.isValid()) - { - rconfig.ival(0) = raceId; - rconfig.ival(1) = isWatched; - rconfig.ival(2) = fk; - rconfig.ival(3) = mk; - rconfig.ival(4) = fa; - rconfig.ival(5) = ma; - } - else + if (!initialized) { - // this should never happen - string keyname = "autobutcher/watchlist/" + getRaceNameById(raceId); - out << "Something failed, could not find/create config key " << keyname << "!" << endl; - } - } + this->mode = mode; + saved_ui_building_assign_type.clear(); + saved_ui_building_assign_units.clear(); + saved_ui_building_assign_items.clear(); + saved_ui_building_assign_is_marked.clear(); + saved_indexes.clear(); - void RemoveConfig(color_ostream & out) - { - if(!rconfig.isValid()) - return; - World::DeletePersistentData(rconfig); - } + for (size_t i = 0; i < ui_building_assign_units->size(); i++) + { + saved_ui_building_assign_type.push_back(ui_building_assign_type->at(i)); + saved_ui_building_assign_units.push_back(ui_building_assign_units->at(i)); + saved_ui_building_assign_items.push_back(ui_building_assign_items->at(i)); + saved_ui_building_assign_is_marked.push_back(ui_building_assign_is_marked->at(i)); + } - void SortUnitsByAge() - { - sort(unit_ptr[fk_index].begin(), unit_ptr[fk_index].end(), compareUnitAgesOlder); - sort(unit_ptr[mk_index].begin(), unit_ptr[mk_index].end(), compareUnitAgesOlder); - sort(unit_ptr[fa_index].begin(), unit_ptr[fa_index].end(), compareUnitAgesYounger); - sort(unit_ptr[ma_index].begin(), unit_ptr[ma_index].end(), compareUnitAgesYounger); - sort(prot_ptr[fk_index].begin(), prot_ptr[fk_index].end(), compareUnitAgesOlder); - sort(prot_ptr[mk_index].begin(), prot_ptr[mk_index].end(), compareUnitAgesOlder); - sort(prot_ptr[fa_index].begin(), prot_ptr[fa_index].end(), compareUnitAgesYounger); - sort(prot_ptr[ma_index].begin(), prot_ptr[ma_index].end(), compareUnitAgesYounger); - } + search_string.clear(); + show_non_grazers = show_pastured = show_noncaged = show_male = show_female = show_other_zones = true; + entry_mode = false; - void PushUnit(df::unit * unit) - { - if(isFemale(unit)) - { - if(isBaby(unit) || isChild(unit)) - unit_ptr[fk_index].push_back(unit); - else - unit_ptr[fa_index].push_back(unit); - } - else //treat sex n/a like it was male - { - if(isBaby(unit) || isChild(unit)) - unit_ptr[mk_index].push_back(unit); - else - unit_ptr[ma_index].push_back(unit); + initialized = true; } } - void PushPriorityUnit(df::unit * unit) + void deinitialize() { - if(isFemale(unit)) - { - if(isBaby(unit) || isChild(unit)) - prot_ptr[fk_index].push_back(unit); - else - prot_ptr[fa_index].push_back(unit); - } - else - { - if(isBaby(unit) || isChild(unit)) - prot_ptr[mk_index].push_back(unit); - else - prot_ptr[ma_index].push_back(unit); - } + initialized = false; } - void PushProtectedUnit(df::unit * unit) + void apply_filters() { - if(isFemale(unit)) - { - if(isBaby(unit) || isChild(unit)) - fk_prot++; - else - fa_prot++; - } - else //treat sex n/a like it was male + if (saved_indexes.size() > 0) { - if(isBaby(unit) || isChild(unit)) - mk_prot++; - else - ma_prot++; - } - } + bool list_has_been_sorted = (ui_building_assign_units->size() == reference_list.size() + && *ui_building_assign_units != reference_list); - void ClearUnits() - { - fk_prot = fa_prot = mk_prot = ma_prot = 0; - for (size_t i = 0; i < 4; i++) - { - unit_ptr[i].clear(); - prot_ptr[i].clear(); - } - } + for (size_t i = 0; i < saved_indexes.size(); i++) + { + int adjusted_item_index = i; + if (list_has_been_sorted) + { + for (size_t j = 0; j < ui_building_assign_units->size(); j++) + { + if (ui_building_assign_units->at(j) == reference_list[i]) + { + adjusted_item_index = j; + break; + } + } + } - int ProcessUnits(vector& unit_ptr, vector& unit_pri_ptr, unsigned prot, unsigned goal) - { - int subcount = 0; - while(unit_pri_ptr.size() && (unit_ptr.size() + unit_pri_ptr.size() + prot > goal) ) - { - df::unit* unit = unit_pri_ptr.back(); - doMarkForSlaughter(unit); - unit_pri_ptr.pop_back(); - subcount++; - } - while(unit_ptr.size() && (unit_ptr.size() + prot > goal) ) - { - df::unit* unit = unit_ptr.back(); - doMarkForSlaughter(unit); - unit_ptr.pop_back(); - subcount++; - } - return subcount; - } - - int ProcessUnits() - { - SortUnitsByAge(); - int slaughter_count = 0; - slaughter_count += ProcessUnits(unit_ptr[fk_index], prot_ptr[fk_index], fk_prot, fk); - slaughter_count += ProcessUnits(unit_ptr[mk_index], prot_ptr[mk_index], mk_prot, mk); - slaughter_count += ProcessUnits(unit_ptr[fa_index], prot_ptr[fa_index], fa_prot, fa); - slaughter_count += ProcessUnits(unit_ptr[ma_index], prot_ptr[ma_index], ma_prot, ma); - ClearUnits(); - return slaughter_count; - } -}; -// vector of races handled by autobutcher -// the name is a bit misleading since entries can be set to 'unwatched' -// to ignore them for a while but still keep the target count settings -std::vector watched_races; - -// helper for sorting the watchlist alphabetically -bool compareRaceNames(WatchedRace* i, WatchedRace* j) -{ - string name_i = getRaceNamePluralById(i->raceId); - string name_j = getRaceNamePluralById(j->raceId); - - return (name_i < name_j); -} - -static void autobutcher_sortWatchList(color_ostream &out); - -// default target values for autobutcher -static unsigned default_fk = 5; -static unsigned default_mk = 1; -static unsigned default_fa = 5; -static unsigned default_ma = 1; - -command_result df_autobutcher(color_ostream &out, vector & parameters) -{ - CoreSuspender suspend; - - bool verbose = false; - bool watch_race = false; - bool unwatch_race = false; - bool forget_race = false; - bool list_watched = false; - bool list_export = false; - bool change_target = false; - vector target_racenames; - vector target_raceids; - - unsigned target_fk = default_fk; - unsigned target_mk = default_mk; - unsigned target_fa = default_fa; - unsigned target_ma = default_ma; - - if(!parameters.size()) - { - out << "You must specify a command!" << endl; - out << autobutcher_help << endl; - return CR_OK; - } - - // parse main command - string & p = parameters[0]; - if (p == "help" || p == "?") - { - out << autobutcher_help << endl; - return CR_OK; - } - if (p == "example") - { - out << autobutcher_help_example << endl; - return CR_OK; - } - else if (p == "start") - { - plugin_enable(out, true); - enable_autobutcher = true; - start_autobutcher(out); - return autoButcher(out, verbose); - } - else if (p == "stop") - { - enable_autobutcher = false; - if(config_autobutcher.isValid()) - config_autobutcher.ival(0) = enable_autobutcher; - out << "Autobutcher stopped." << endl; - return CR_OK; - } - else if(p == "sleep") - { - parameters.erase(parameters.begin()); - if(!parameters.size()) - { - out.printerr("No duration specified!\n"); - return CR_WRONG_USAGE; - } - else - { - size_t ticks = 0; - stringstream ss(parameters.back()); - ss >> ticks; - if(ticks <= 0) - { - out.printerr("Invalid duration specified (must be > 0)!\n"); - return CR_WRONG_USAGE; - } - sleep_autobutcher = ticks; - if(config_autobutcher.isValid()) - config_autobutcher.ival(1) = sleep_autobutcher; - out << "New sleep timer for autobutcher: " << ticks << " ticks." << endl; - return CR_OK; - } - } - else if(p == "watch") - { - parameters.erase(parameters.begin()); - watch_race = true; - out << "Start watching race(s): "; // << endl; - } - else if(p == "unwatch") - { - parameters.erase(parameters.begin()); - unwatch_race = true; - out << "Stop watching race(s): "; // << endl; - } - else if(p == "forget") - { - parameters.erase(parameters.begin()); - forget_race = true; - out << "Removing race(s) from watchlist: "; // << endl; - } - else if(p == "target") - { - // needs at least 5 more parameters: - // fk mk fa ma R (can have more than 1 R) - if(parameters.size() < 6) - { - out.printerr("Not enough parameters!\n"); - return CR_WRONG_USAGE; - } - else - { - stringstream fk(parameters[1]); - stringstream mk(parameters[2]); - stringstream fa(parameters[3]); - stringstream ma(parameters[4]); - fk >> target_fk; - mk >> target_mk; - fa >> target_fa; - ma >> target_ma; - parameters.erase(parameters.begin(), parameters.begin()+5); - change_target = true; - out << "Setting new target count for race(s): "; // << endl; - } - } - else if(p == "autowatch") - { - out << "Auto-adding to watchlist started." << endl; - enable_autobutcher_autowatch = true; - if(config_autobutcher.isValid()) - config_autobutcher.ival(2) = enable_autobutcher_autowatch; - return CR_OK; - } - else if(p == "noautowatch") - { - out << "Auto-adding to watchlist stopped." << endl; - enable_autobutcher_autowatch = false; - if(config_autobutcher.isValid()) - config_autobutcher.ival(2) = enable_autobutcher_autowatch; - return CR_OK; - } - else if(p == "list") - { - list_watched = true; - } - else if(p == "list_export") - { - list_export = true; - } - else - { - out << "Unknown command: " << p << endl; - return CR_WRONG_USAGE; - } - - if(list_watched) - { - out << "Autobutcher status: "; - - if(enable_autobutcher) - out << "enabled,"; - else - out << "not enabled,"; - - if (enable_autobutcher_autowatch) - out << " autowatch,"; - else - out << " noautowatch,"; - - out << " sleep: " << sleep_autobutcher << endl; - - out << "Default setting for new races:" - << " fk=" << default_fk - << " mk=" << default_mk - << " fa=" << default_fa - << " ma=" << default_ma - << endl; - - if(!watched_races.size()) - { - out << "The autobutcher race list is empty." << endl; - return CR_OK; - } - - out << "Races on autobutcher list: " << endl; - for(size_t i=0; iraws.creatures.all[w->raceId]; - string name = raw->creature_id; - if(w->isWatched) - out << "watched: "; - else - out << "not watched: "; - out << name - << " fk=" << w->fk - << " mk=" << w->mk - << " fa=" << w->fa - << " ma=" << w->ma - << endl; - } - return CR_OK; - } - - if(list_export) - { - // force creation of config - out << "autobutcher start" << endl; - - if(!enable_autobutcher) - out << "autobutcher stop" << endl; - - if (enable_autobutcher_autowatch) - out << "autobutcher autowatch" << endl; - - out << "autobutcher sleep " << sleep_autobutcher << endl; - out << "autobutcher target" - << " " << default_fk - << " " << default_mk - << " " << default_fa - << " " << default_ma - << " new" << endl; - - for(size_t i=0; iraws.creatures.all[w->raceId]; - string name = raw->creature_id; - - out << "autobutcher target" - << " " << w->fk - << " " << w->mk - << " " << w->fa - << " " << w->ma - << " " << name << endl; - - if(w->isWatched) - out << "autobutcher watch " << name << endl; - } - return CR_OK; - } - - // parse rest of parameters for commands followed by a list of races - if( watch_race - || unwatch_race - || forget_race - || change_target ) - { - if(!parameters.size()) - { - out.printerr("No race(s) specified!\n"); - return CR_WRONG_USAGE; - } - while(parameters.size()) - { - string tr = parameters.back(); - target_racenames.push_back(tr); - parameters.pop_back(); - out << tr << " "; - } - out << endl; - } - - if(change_target && target_racenames.size() && target_racenames[0] == "all") - { - out << "Setting target count for all races on watchlist." << endl; - for(size_t i=0; ifk = target_fk; - w->mk = target_mk; - w->fa = target_fa; - w->ma = target_ma; - w->UpdateConfig(out); - } - } - - if(target_racenames.size() && (target_racenames[0] == "all" || target_racenames[0] == "new")) - { - if(change_target) - { - out << "Setting target count for the future." << endl; - default_fk = target_fk; - default_mk = target_mk; - default_fa = target_fa; - default_ma = target_ma; - if(config_autobutcher.isValid()) - { - config_autobutcher.ival(3) = default_fk; - config_autobutcher.ival(4) = default_mk; - config_autobutcher.ival(5) = default_fa; - config_autobutcher.ival(6) = default_ma; - } - return CR_OK; - } - else if(target_racenames[0] == "new") - { - out << "The only valid usage of 'new' is in combination when setting a target count!" << endl; - - // hm, maybe instead of complaining start/stop autowatch instead? and get rid of the autowatch option? - if(unwatch_race) - out << "'unwatch new' makes no sense! Use 'noautowatch' instead." << endl; - else if(forget_race) - out << "'forget new' makes no sense, 'forget' is only for existing watchlist entries! Use 'noautowatch' instead." << endl; - else if(watch_race) - out << "'watch new' makes no sense! Use 'autowatch' instead." << endl; - return CR_WRONG_USAGE; - } - } - - if(target_racenames.size() && target_racenames[0] == "all") - { - // fill with race ids from watchlist - for(size_t i=0; iraceId); - } - } - else - { - // map race names from parameter list to ids - size_t num_races = world->raws.creatures.all.size(); - while(target_racenames.size()) - { - bool found_race = false; - for(size_t i=0; iraceId == target_raceids.back()) - { - if(unwatch_race) - { - w->isWatched=false; - w->UpdateConfig(out); - } - else if(forget_race) - { - w->RemoveConfig(out); - watched_races.erase(watched_races.begin()+i); - } - else if(watch_race) - { - w->isWatched = true; - w->UpdateConfig(out); - } - else if(change_target) - { - w->fk = target_fk; - w->mk = target_mk; - w->fa = target_fa; - w->ma = target_ma; - w->UpdateConfig(out); - } - entry_found = true; - break; - } - } - if(!entry_found && (watch_race||change_target)) - { - WatchedRace * w = new WatchedRace(watch_race, target_raceids.back(), target_fk, target_mk, target_fa, target_ma); - w->UpdateConfig(out); - watched_races.push_back(w); - autobutcher_sortWatchList(out); - } - target_raceids.pop_back(); - } - - return CR_OK; -} - -// check watched_races vector for a race id, return -1 if nothing found -// calling method needs to check itself if the race is currently being watched or ignored -int getWatchedIndex(int id) -{ - for(size_t i=0; iraceId == id) // && w->isWatched) - return i; - } - return -1; -} - -command_result autoButcher( color_ostream &out, bool verbose = false ) -{ - // don't run if not supposed to - if(!Maps::IsValid()) - return CR_OK; - - // check if there is anything to watch before walking through units vector - if(!enable_autobutcher_autowatch) - { - bool watching = false; - for(size_t i=0; iisWatched) - { - watching = true; - break; - } - } - if(!watching) - return CR_OK; - } - - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - // this check is now divided into two steps, squeezed autowatch into the middle - // first one ignores completely inappropriate units (dead, undead, not belonging to the fort, ...) - // then let autowatch add units to the watchlist which will probably start breeding (owned pets, war animals, ...) - // then process units counting those which can't be butchered (war animals, named pets, ...) - // so that they are treated as "own stock" as well and count towards the target quota - if( !isActive(unit) - || isUndead(unit) - || isMarkedForSlaughter(unit) - || isMerchant(unit) // ignore merchants' draught animals - || isForest(unit) // ignore merchants' caged animals - || !isOwnCiv(unit) - || !isTame(unit) - ) - continue; - - // found a bugged unit which had invalid coordinates but was not in a cage. - // marking it for slaughter didn't seem to have negative effects, but you never know... - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - WatchedRace * w = NULL; - int watched_index = getWatchedIndex(unit->race); - if(watched_index != -1) - { - w = watched_races[watched_index]; - } - else if(enable_autobutcher_autowatch) - { - w = new WatchedRace(true, unit->race, default_fk, default_mk, default_fa, default_ma); - w->UpdateConfig(out); - watched_races.push_back(w); - - string announce; - announce = "New race added to autobutcher watchlist: " + getRaceNamePluralById(w->raceId); - Gui::showAnnouncement(announce, 2, false); - autobutcher_sortWatchList(out); - } - - if(w && w->isWatched) - { - // don't butcher protected units, but count them as stock as well - // this way they count towards target quota, so if you order that you want 1 female adult cat - // and have 2 cats, one of them being a pet, the other gets butchered - if( isWar(unit) // ignore war dogs etc - || isHunter(unit) // ignore hunting dogs etc - // ignore creatures in built cages which are defined as rooms to leave zoos alone - // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) - || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() - || isAvailableForAdoption(unit) - || unit->name.has_name ) - w->PushProtectedUnit(unit); - else if ( isGay(unit) - || isGelded(unit)) - w->PushPriorityUnit(unit); - else - w->PushUnit(unit); - } - } - - int slaughter_count = 0; - for(size_t i=0; iProcessUnits(); - slaughter_count += slaughter_subcount; - if(slaughter_subcount) - { - stringstream ss; - ss << slaughter_subcount; - string announce; - announce = getRaceNamePluralById(w->raceId) + " marked for slaughter: " + ss.str(); - Gui::showAnnouncement(announce, 2, false); - } - } - - return CR_OK; -} - -//////////////////////////////////////////////////// -// autobutcher start/init/cleanup - -command_result start_autobutcher(color_ostream &out) -{ - plugin_enable(out, true); - enable_autobutcher = true; - - if (!config_autobutcher.isValid()) - { - config_autobutcher = World::AddPersistentData("autobutcher/config"); - - if (!config_autobutcher.isValid()) - { - out << "Cannot enable autobutcher without a world!" << endl; - return CR_OK; - } - - config_autobutcher.ival(1) = sleep_autobutcher; - config_autobutcher.ival(2) = enable_autobutcher_autowatch; - config_autobutcher.ival(3) = default_fk; - config_autobutcher.ival(4) = default_mk; - config_autobutcher.ival(5) = default_fa; - config_autobutcher.ival(6) = default_ma; - } - - config_autobutcher.ival(0) = enable_autobutcher; - - out << "Starting autobutcher." << endl; - init_autobutcher(out); - return CR_OK; -} - -command_result init_autobutcher(color_ostream &out) -{ - cleanup_autobutcher(out); - - config_autobutcher = World::GetPersistentData("autobutcher/config"); - if(config_autobutcher.isValid()) - { - if (config_autobutcher.ival(0) == -1) - { - config_autobutcher.ival(0) = enable_autobutcher; - config_autobutcher.ival(1) = sleep_autobutcher; - config_autobutcher.ival(2) = enable_autobutcher_autowatch; - config_autobutcher.ival(3) = default_fk; - config_autobutcher.ival(4) = default_mk; - config_autobutcher.ival(5) = default_fa; - config_autobutcher.ival(6) = default_ma; - out << "Autobutcher's persistent config object was invalid!" << endl; - } - else - { - enable_autobutcher = config_autobutcher.ival(0); - sleep_autobutcher = config_autobutcher.ival(1); - enable_autobutcher_autowatch = config_autobutcher.ival(2); - default_fk = config_autobutcher.ival(3); - default_mk = config_autobutcher.ival(4); - default_fa = config_autobutcher.ival(5); - default_ma = config_autobutcher.ival(6); - } - } - - if(!enable_autobutcher) - return CR_OK; - - plugin_enable(out, true); - // read watchlist from save - - std::vector items; - World::GetPersistentData(&items, "autobutcher/watchlist/", true); - for (auto p = items.begin(); p != items.end(); p++) - { - string key = p->key(); - out << "Reading from save: " << key << endl; - //out << " raceid: " << p->ival(0) << endl; - //out << " watched: " << p->ival(1) << endl; - //out << " fk: " << p->ival(2) << endl; - //out << " mk: " << p->ival(3) << endl; - //out << " fa: " << p->ival(4) << endl; - //out << " ma: " << p->ival(5) << endl; - WatchedRace * w = new WatchedRace(p->ival(1), p->ival(0), p->ival(2), p->ival(3),p->ival(4),p->ival(5)); - w->rconfig = *p; - watched_races.push_back(w); - } - autobutcher_sortWatchList(out); - return CR_OK; -} - -command_result cleanup_autobutcher(color_ostream &out) -{ - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - if(unit->race != race) - continue; - - if( !isActive(unit) - || isUndead(unit) - || isMerchant(unit) // ignore merchants' draught animals - || isForest(unit) // ignore merchants' caged animals - || !isOwnCiv(unit) - ) - continue; - - // found a bugged unit which had invalid coordinates but was not in a cage. - // marking it for slaughter didn't seem to have negative effects, but you never know... - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - w->PushUnit(unit); - } - return w; -} - -WatchedRace * checkRaceStocksProtected(int race) -{ - WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma); - - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - if(unit->race != race) - continue; - - if( !isActive(unit) - || isUndead(unit) - || isMerchant(unit) // ignore merchants' draught animals - || isForest(unit) // ignore merchants' caged animals - || !isOwnCiv(unit) - ) - continue; - - // found a bugged unit which had invalid coordinates but was not in a cage. - // marking it for slaughter didn't seem to have negative effects, but you never know... - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - if( !isTame(unit) - || isWar(unit) // ignore war dogs etc - || isHunter(unit) // ignore hunting dogs etc - // ignore creatures in built cages which are defined as rooms to leave zoos alone - // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) - || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() - || isAvailableForAdoption(unit) - || unit->name.has_name ) - w->PushUnit(unit); - } - return w; -} - -WatchedRace * checkRaceStocksButcherable(int race) -{ - WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma); - - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - if(unit->race != race) - continue; - - if( !isActive(unit) - || isUndead(unit) - || isMerchant(unit) // ignore merchants' draught animals - || isForest(unit) // ignore merchants' caged animals - || !isOwnCiv(unit) - || !isTame(unit) - || isWar(unit) // ignore war dogs etc - || isHunter(unit) // ignore hunting dogs etc - // ignore creatures in built cages which are defined as rooms to leave zoos alone - // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) - || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() - || isAvailableForAdoption(unit) - || unit->name.has_name - ) - continue; - - // found a bugged unit which had invalid coordinates but was not in a cage. - // marking it for slaughter didn't seem to have negative effects, but you never know... - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - w->PushUnit(unit); - } - return w; -} - -WatchedRace * checkRaceStocksButcherFlag(int race) -{ - WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma); - - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - if(unit->race != race) - continue; - - if( !isActive(unit) - || isUndead(unit) - || isMerchant(unit) // ignore merchants' draught animals - || isForest(unit) // ignore merchants' caged animals - || !isOwnCiv(unit) - ) - continue; - - // found a bugged unit which had invalid coordinates but was not in a cage. - // marking it for slaughter didn't seem to have negative effects, but you never know... - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - if(isMarkedForSlaughter(unit)) - w->PushUnit(unit); - } - return w; -} - -void butcherRace(int race) -{ - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - if(unit->race != race) - continue; - - if( !isActive(unit) - || isUndead(unit) - || isMerchant(unit) // ignore merchants' draught animals - || isForest(unit) // ignore merchants' caged animals - || !isOwnCiv(unit) - || !isTame(unit) - || isWar(unit) // ignore war dogs etc - || isHunter(unit) // ignore hunting dogs etc - // ignore creatures in built cages which are defined as rooms to leave zoos alone - // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) - || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() - || isAvailableForAdoption(unit) - || unit->name.has_name - ) - continue; - - // found a bugged unit which had invalid coordinates but was not in a cage. - // marking it for slaughter didn't seem to have negative effects, but you never know... - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - doMarkForSlaughter(unit); - } -} - -// remove butcher flag for all units of a given race -void unbutcherRace(int race) -{ - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - if(unit->race != race) - continue; - - if( !isActive(unit) - || isUndead(unit) - || !isMarkedForSlaughter(unit) - ) - continue; - - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - unit->flags2.bits.slaughter = 0; - } -} - - -///////////////////////////////////// -// API functions to control autobutcher with a lua script - -static bool autobutcher_isEnabled() { return enable_autobutcher; } -static bool autowatch_isEnabled() { return enable_autobutcher_autowatch; } - -static unsigned autobutcher_getSleep(color_ostream &out) -{ - return sleep_autobutcher; -} - -static void autobutcher_setSleep(color_ostream &out, unsigned ticks) -{ - sleep_autobutcher = ticks; - if(config_autobutcher.isValid()) - config_autobutcher.ival(1) = sleep_autobutcher; -} - -static void autobutcher_setEnabled(color_ostream &out, bool enable) -{ - if(enable) - { - enable_autobutcher = true; - start_autobutcher(out); - autoButcher(out, false); - } - else - { - enable_autobutcher = false; - if(config_autobutcher.isValid()) - config_autobutcher.ival(0) = enable_autobutcher; - out << "Autobutcher stopped." << endl; - } - - if (enable) - plugin_enable(out, true); -} - -static void autowatch_setEnabled(color_ostream &out, bool enable) -{ - if(enable) - { - out << "Auto-adding to watchlist started." << endl; - enable_autobutcher_autowatch = true; - if(config_autobutcher.isValid()) - config_autobutcher.ival(2) = enable_autobutcher_autowatch; - } - else - { - out << "Auto-adding to watchlist stopped." << endl; - enable_autobutcher_autowatch = false; - if(config_autobutcher.isValid()) - config_autobutcher.ival(2) = enable_autobutcher_autowatch; - } -} - -// set all data for a watchlist race in one go -// if race is not already on watchlist it will be added -// params: (id, fk, mk, fa, ma, watched) -static void autobutcher_setWatchListRace(color_ostream &out, unsigned id, unsigned fk, unsigned mk, unsigned fa, unsigned ma, bool watched) -{ - int watched_index = getWatchedIndex(id); - if(watched_index != -1) - { - out << "updating watchlist entry" << endl; - WatchedRace * w = watched_races[watched_index]; - w->fk = fk; - w->mk = mk; - w->fa = fa; - w->ma = ma; - w->isWatched = watched; - w->UpdateConfig(out); - } - else - { - out << "creating new watchlist entry" << endl; - WatchedRace * w = new WatchedRace(watched, id, fk, mk, fa, ma); //default_fk, default_mk, default_fa, default_ma); - w->UpdateConfig(out); - watched_races.push_back(w); - - string announce; - announce = "New race added to autobutcher watchlist: " + getRaceNamePluralById(w->raceId); - Gui::showAnnouncement(announce, 2, false); - autobutcher_sortWatchList(out); - } -} - -// remove entry from watchlist -static void autobutcher_removeFromWatchList(color_ostream &out, unsigned id) -{ - int watched_index = getWatchedIndex(id); - if(watched_index != -1) - { - out << "updating watchlist entry" << endl; - WatchedRace * w = watched_races[watched_index]; - w->RemoveConfig(out); - watched_races.erase(watched_races.begin() + watched_index); - } -} - -// sort watchlist alphabetically -static void autobutcher_sortWatchList(color_ostream &out) -{ - sort(watched_races.begin(), watched_races.end(), compareRaceNames); -} - -// set default target values for new races -static void autobutcher_setDefaultTargetNew(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma) -{ - default_fk = fk; - default_mk = mk; - default_fa = fa; - default_ma = ma; - if(config_autobutcher.isValid()) - { - config_autobutcher.ival(3) = default_fk; - config_autobutcher.ival(4) = default_mk; - config_autobutcher.ival(5) = default_fa; - config_autobutcher.ival(6) = default_ma; - } -} - -// set default target values for ALL races (update watchlist and set new default) -static void autobutcher_setDefaultTargetAll(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma) -{ - for(unsigned i=0; ifk = fk; - w->mk = mk; - w->fa = fa; - w->ma = ma; - w->UpdateConfig(out); - } - autobutcher_setDefaultTargetNew(out, fk, mk, fa, ma); -} - -static void autobutcher_butcherRace(color_ostream &out, unsigned id) -{ - butcherRace(id); -} - -static void autobutcher_unbutcherRace(color_ostream &out, unsigned id) -{ - unbutcherRace(id); -} - -// push autobutcher settings on lua stack -static int autobutcher_getSettings(lua_State *L) -{ - lua_newtable(L); - int ctable = lua_gettop(L); - Lua::SetField(L, enable_autobutcher, ctable, "enable_autobutcher"); - Lua::SetField(L, enable_autobutcher_autowatch, ctable, "enable_autowatch"); - Lua::SetField(L, default_fk, ctable, "fk"); - Lua::SetField(L, default_mk, ctable, "mk"); - Lua::SetField(L, default_fa, ctable, "fa"); - Lua::SetField(L, default_ma, ctable, "ma"); - Lua::SetField(L, sleep_autobutcher, ctable, "sleep"); - return 1; -} - -// push the watchlist vector as nested table on the lua stack -static int autobutcher_getWatchList(lua_State *L) -{ - lua_newtable(L); - - for(size_t i=0; iraceId, ctable, "id"); - Lua::SetField(L, w->isWatched, ctable, "watched"); - Lua::SetField(L, getRaceNamePluralById(w->raceId), ctable, "name"); - Lua::SetField(L, w->fk, ctable, "fk"); - Lua::SetField(L, w->mk, ctable, "mk"); - Lua::SetField(L, w->fa, ctable, "fa"); - Lua::SetField(L, w->ma, ctable, "ma"); - - int id = w->raceId; - - w = checkRaceStocksTotal(id); - Lua::SetField(L, w->unit_ptr[fk_index].size(), ctable, "fk_total"); - Lua::SetField(L, w->unit_ptr[mk_index].size(), ctable, "mk_total"); - Lua::SetField(L, w->unit_ptr[fa_index].size(), ctable, "fa_total"); - Lua::SetField(L, w->unit_ptr[ma_index].size(), ctable, "ma_total"); - delete w; - - w = checkRaceStocksProtected(id); - Lua::SetField(L, w->unit_ptr[fk_index].size(), ctable, "fk_protected"); - Lua::SetField(L, w->unit_ptr[mk_index].size(), ctable, "mk_protected"); - Lua::SetField(L, w->unit_ptr[fa_index].size(), ctable, "fa_protected"); - Lua::SetField(L, w->unit_ptr[ma_index].size(), ctable, "ma_protected"); - delete w; - - w = checkRaceStocksButcherable(id); - Lua::SetField(L, w->unit_ptr[fk_index].size(), ctable, "fk_butcherable"); - Lua::SetField(L, w->unit_ptr[mk_index].size(), ctable, "mk_butcherable"); - Lua::SetField(L, w->unit_ptr[fa_index].size(), ctable, "fa_butcherable"); - Lua::SetField(L, w->unit_ptr[ma_index].size(), ctable, "ma_butcherable"); - delete w; - - w = checkRaceStocksButcherFlag(id); - Lua::SetField(L, w->unit_ptr[fk_index].size(), ctable, "fk_butcherflag"); - Lua::SetField(L, w->unit_ptr[mk_index].size(), ctable, "mk_butcherflag"); - Lua::SetField(L, w->unit_ptr[fa_index].size(), ctable, "fa_butcherflag"); - Lua::SetField(L, w->unit_ptr[ma_index].size(), ctable, "ma_butcherflag"); - delete w; - - lua_rawseti(L, -2, i+1); - } - - return 1; -} - -DFHACK_PLUGIN_LUA_FUNCTIONS { - DFHACK_LUA_FUNCTION(autobutcher_isEnabled), - DFHACK_LUA_FUNCTION(autowatch_isEnabled), - DFHACK_LUA_FUNCTION(autobutcher_setEnabled), - DFHACK_LUA_FUNCTION(autowatch_setEnabled), - DFHACK_LUA_FUNCTION(autobutcher_getSleep), - DFHACK_LUA_FUNCTION(autobutcher_setSleep), - DFHACK_LUA_FUNCTION(autobutcher_setWatchListRace), - DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetNew), - DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetAll), - DFHACK_LUA_FUNCTION(autobutcher_butcherRace), - DFHACK_LUA_FUNCTION(autobutcher_unbutcherRace), - DFHACK_LUA_FUNCTION(autobutcher_removeFromWatchList), - DFHACK_LUA_FUNCTION(autobutcher_sortWatchList), - DFHACK_LUA_END -}; - -DFHACK_PLUGIN_LUA_COMMANDS { - DFHACK_LUA_COMMAND(autobutcher_getSettings), - DFHACK_LUA_COMMAND(autobutcher_getWatchList), - DFHACK_LUA_END -}; - -// end lua API - - - -//START zone filters - -class zone_filter -{ -public: - zone_filter() - { - initialized = false; - } - - void initialize(const df::ui_sidebar_mode &mode) - { - if (!initialized) - { - this->mode = mode; - saved_ui_building_assign_type.clear(); - saved_ui_building_assign_units.clear(); - saved_ui_building_assign_items.clear(); - saved_ui_building_assign_is_marked.clear(); - saved_indexes.clear(); - - for (size_t i = 0; i < ui_building_assign_units->size(); i++) - { - saved_ui_building_assign_type.push_back(ui_building_assign_type->at(i)); - saved_ui_building_assign_units.push_back(ui_building_assign_units->at(i)); - saved_ui_building_assign_items.push_back(ui_building_assign_items->at(i)); - saved_ui_building_assign_is_marked.push_back(ui_building_assign_is_marked->at(i)); - } - - search_string.clear(); - show_non_grazers = show_pastured = show_noncaged = show_male = show_female = show_other_zones = true; - entry_mode = false; - - initialized = true; - } - } - - void deinitialize() - { - initialized = false; - } - - void apply_filters() - { - if (saved_indexes.size() > 0) - { - bool list_has_been_sorted = (ui_building_assign_units->size() == reference_list.size() - && *ui_building_assign_units != reference_list); - - for (size_t i = 0; i < saved_indexes.size(); i++) - { - int adjusted_item_index = i; - if (list_has_been_sorted) - { - for (size_t j = 0; j < ui_building_assign_units->size(); j++) - { - if (ui_building_assign_units->at(j) == reference_list[i]) - { - adjusted_item_index = j; - break; - } - } - } - - saved_ui_building_assign_is_marked[saved_indexes[i]] = ui_building_assign_is_marked->at(adjusted_item_index); - } + saved_ui_building_assign_is_marked[saved_indexes[i]] = ui_building_assign_is_marked->at(adjusted_item_index); + } } string search_string_l = toLower(search_string); @@ -3578,7 +1979,7 @@ public: if (!curr_unit) continue; - if (!show_non_grazers && !isGrazer(curr_unit)) + if (!show_non_grazers && !Units::isGrazer(curr_unit)) continue; if (!show_pastured && isAssignedToZone(curr_unit)) @@ -3594,10 +1995,10 @@ public: continue; } - if (!show_male && isMale(curr_unit)) + if (!show_male && Units::isMale(curr_unit)) continue; - if (!show_female && isFemale(curr_unit)) + if (!show_female && Units::isFemale(curr_unit)) continue; if (!search_string_l.empty()) @@ -3894,21 +2295,15 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "zone", "manage activity zones.", - df_zone, false, - zone_help.c_str() - )); - commands.push_back(PluginCommand( - "autobutcher", "auto-assign lifestock for butchering.", - df_autobutcher, false, - autobutcher_help.c_str() - )); - init_autobutcher(out); + "zone", + "manage activity zones.", + df_zone, + false, + zone_help.c_str())); return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { - cleanup_autobutcher(out); return CR_OK; } From fe2212db96244c056e00a374bba5269aad3e49fc Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 01:07:36 -0700 Subject: [PATCH 420/854] output status when run without params --- plugins/autonestbox.cpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index 6c878806c..b1932b269 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -41,12 +41,14 @@ static const string autonestbox_help = "Usage:\n" "\n" "enable autonestbox\n" - " Start checking for unpastured egg-layers and assigning them to nestbox zones.\n" + " Start checking for unpastured egg-layers and assigning them to nestbox zones.\n" + "autonestbox\n" + " Print current status." "autonestbox now\n" - " Run a scan and assignment cycle right now. Does not require that the plugin is enabled.\n" + " Run a scan and assignment cycle right now. Does not require that the plugin is enabled.\n" "autonestbox ticks \n" - " Change the number of ticks between scan and assignment cycles when the plugin is enabled.\n" - " The default is 6000 (about 8 days)\n"; + " Change the number of ticks between scan and assignment cycles when the plugin is enabled.\n" + " The default is 6000 (about 8 days)\n"; namespace DFHack { DBG_DECLARE(autonestbox, status); @@ -56,8 +58,8 @@ namespace DFHack { static const string CONFIG_KEY = "autonestbox/config"; static PersistentDataItem config; enum ConfigValues { - IS_ENABLED = 0, - CYCLE_TICKS = 1, + CONFIG_IS_ENABLED = 0, + CONFIG_CYCLE_TICKS = 1, }; static int get_config_val(int index) { if (!config.isValid()) @@ -103,15 +105,15 @@ static void init_autonestbox(color_ostream &out) { if (!config.isValid()) config = World::AddPersistentData(CONFIG_KEY); - if (get_config_val(IS_ENABLED) == -1) { - set_config_val(IS_ENABLED, 0); - set_config_val(CYCLE_TICKS, 6000); + if (get_config_val(CONFIG_IS_ENABLED) == -1) { + set_config_val(CONFIG_IS_ENABLED, 0); + set_config_val(CONFIG_CYCLE_TICKS, 6000); } if (is_enabled) - set_config_val(IS_ENABLED, 1); + set_config_val(CONFIG_IS_ENABLED, 1); else - is_enabled = (get_config_val(IS_ENABLED) == 1); + is_enabled = (get_config_val(CONFIG_IS_ENABLED) == 1); did_complain = false; } @@ -167,7 +169,8 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan } DFhackCExport command_result plugin_onupdate(color_ostream &out) { - if (is_enabled && ++cycle_counter >= (size_t)get_config_val(CYCLE_TICKS)) + if (is_enabled && ++cycle_counter >= + (size_t)get_config_val(CONFIG_CYCLE_TICKS)) autonestbox_cycle(out); return CR_OK; } @@ -209,12 +212,15 @@ static command_result df_autonestbox(color_ostream &out, vector ¶met return CR_WRONG_USAGE; if (opts.ticks > -1) { - set_config_val(CYCLE_TICKS, opts.ticks); + set_config_val(CONFIG_CYCLE_TICKS, opts.ticks); INFO(status,out).print("New cycle timer: %d ticks.\n", opts.ticks); } else if (opts.now) { autonestbox_cycle(out); } + else { + out << "autonestbox is " << (is_enabled ? "" : "not ") << "running\n"; + } return CR_OK; } From 3983b4d75b41c31510c42ae833825514b730232a Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 01:54:21 -0700 Subject: [PATCH 421/854] update docs --- plugins/autobutcher.cpp | 121 +++++++++++++++++++++--------------- plugins/autonestbox.cpp | 1 - plugins/lua/autobutcher.lua | 9 --- 3 files changed, 72 insertions(+), 59 deletions(-) diff --git a/plugins/autobutcher.cpp b/plugins/autobutcher.cpp index 0d7a9c632..69799207a 100644 --- a/plugins/autobutcher.cpp +++ b/plugins/autobutcher.cpp @@ -34,63 +34,86 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); const string autobutcher_help = - "Assigns your lifestock for slaughter once it reaches a specific count. Requires\n" + "Automatically butcher excess livestock. This plugin monitors how many pets\n" + "you have of each gender and age and assigns excess lifestock for slaughter\n" + "once they reach a specific count. Requires\n" "that you add the target race(s) to a watch list. Only tame units will be\n" "processed. Named units will be completely ignored (you can give animals\n" "nicknames with the tool 'rename unit' to protect them from getting slaughtered\n" - "automatically. Trained war or hunting pets will be ignored.\n" - "Once you have too much adults, the oldest will be butchered first.\n" - "Once you have too much kids, the youngest will be butchered first.\n" + "automatically). Trained war or hunting pets will be ignored.\n" + "Once you have too many adults, the oldest will be butchered first.\n" + "Once you have too many kids, the youngest will be butchered first.\n" "If you don't set a target count the following default will be used:\n" "1 male kid, 5 female kids, 1 male adult, 5 female adults.\n" - "Options:\n" - " start - run every X frames (df simulation ticks)\n" - " default: X=6000 (~60 seconds at 100fps)\n" - " stop - stop running automatically\n" - " sleep X - change timer to sleep X frames between runs.\n" - " watch R - start watching race(s)\n" - " R = valid race RAW id (ALPACA, BIRD_TURKEY, etc)\n" - " or a list of RAW ids seperated by spaces\n" - " or the keyword 'all' which affects your whole current watchlist.\n" - " unwatch R - stop watching race(s)\n" - " the current target settings will be remembered\n" - " forget R - unwatch race(s) and forget target settings for it/them\n" - " autowatch - automatically adds all new races (animals you buy\n" - " from merchants, tame yourself or get from migrants)\n" - " to the watch list using default target count\n" - " noautowatch - stop auto-adding new races to the watch list\n" - " list - print status and watchlist\n" - " list_export - print status and watchlist in batchfile format\n" - " can be used to copy settings into another savegame\n" - " usage: 'dfhack-run autobutcher list_export > xyz.bat' \n" - " target fk mk fa ma R\n" - " - set target count for specified race:\n" - " fk = number of female kids\n" - " mk = number of male kids\n" - " fa = number of female adults\n" - " ma = number of female adults\n" - " R = 'all' sets count for all races on the current watchlist\n" - " including the races which are currenly set to 'unwatched'\n" - " and sets the new default for future watch commands\n" - " R = 'new' sets the new default for future watch commands\n" - " without changing your current watchlist\n" - " example - print some usage examples\n"; - -const string autobutcher_help_example = + "\n" + "Usage:\n" + "\n" + "enable autobutcher\n" + " Start processing livestock according to the configuration. Note that\n" + " no races are watched by default. You have to add the ones you want to\n" + " monitor (or use autowatch)\n" + "autobutcher autowatch\n" + " Automatically add all new races (animals you buy\n" + " from merchants, tame yourself, or get from migrants)\n" + " to the watch list using the default target counts.\n" + "autobutcher noautowatch\n" + " Stop auto-adding new races to the watch list.\n" + "autobutcher target all|new| [ ...] \n" + " Set target counts for the specified races:\n" + " fk = number of female kids\n" + " mk = number of male kids\n" + " fa = number of female adults\n" + " ma = number of female adults\n" + " If you specify 'all', then this command will set the counts for all races\n" + " on your current watchlist (including the races which are currenly set to\n" + " 'unwatched') and sets the new default for future watch commands. If you\n" + " specify 'new', then this command just sets the new default counts for\n" + " future watch commands without changing your current watchlist. Otherwise,\n" + " all space separated races listed will be modified (or added to the watchlist\n" + " if they aren't there already).\n" + "autobutcher ticks \n" + " Change the number of ticks between scanning cycles when the plugin is\n" + " enabled. By default, a cycle happens every 6000 ticks (about 8 game days).\n" + "autobutcher watch all| [ ...]\n" + " Start watching the listed races. If they aren't already in your watchlist, then\n" + " they will be added with the default target counts. If you specify the keyword 'all',\n" + " then all races in your watchlist that are currently marked as unwatched will become\n" + " watched.\n" + "autobutcher unwatch all| [ ...]\n" + " Stop watching the specified race(s) (or all races on your watchlist if 'all' is\n" + " given). The current target settings will be remembered.\n" + "autobutcher forget all| [ ...]\n" + " Unwatch the specified race(s) (or all races on your watchlist if 'all' is given)\n" + " and forget target settings for it/them.\n" + "autobutcher [list]\n" + " Print status and current settings, including the watchlist.\n" + "autobutcher list_export\n" + " Print commands required to set the current settings in another fort.\n" + " Useful to run form dfhack-run like: 'dfhack-run autobutcher list_export > autobutcher.script'\n" + "\n" + "To see a list of all races, run this command:\n" + "\n" + " devel/query --table df.global.world.raws.creatures.all --search ^creature_id --maxdepth 1'\n" + "\n" + "Though not all the races listed there are tameable/butcherable\n" + "\n" "Examples:\n" - " autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY\n" - " autobutcher watch ALPACA BIRD_TURKEY\n" - " autobutcher start\n" - " This means you want to have max 7 kids (4 female, 3 male) and max 3 adults\n" - " (2 female, 1 male) of the races alpaca and turkey. Once the kids grow up the\n" + "\n" + "autobutcher target 4 3 2 1 BIRD_TURKEY\n" + " This means you want to have at most 7 kids (4 female, 3 male) and at most 3 adults\n" + " (2 female, 1 male) for turkeys. Once the kids grow up, the\n" " oldest adults will get slaughtered. Excess kids will get slaughtered starting\n" " the the youngest to allow that the older ones grow into adults.\n" - " autobutcher target 0 0 0 0 new\n" - " autobutcher autowatch\n" - " autobutcher start\n" - " This tells autobutcher to automatically put all new races onto the watchlist\n" - " and mark unnamed tame units for slaughter as soon as they arrive in your\n" - " fortress. Settings already made for some races will be left untouched.\n"; + "autobutcher target 2 2 2 2 DOG\n" + "autobutcher target 1 1 2 2 CAT\n" + "autobutcher target 50 50 14 2 BIRD_GOOSE\n" + "autobutcher target 2 2 4 2 ALPACA SHEEP LLAMA\n" + "autobutcher target 5 5 6 2 PIG\n" + "autobutcher target 0 0 0 0 new\n" + "autobutcher autowatch\n" + " Configure useful limits for dogs, cats, geese (for eggs, leather, and bones), alpacas, sheep,\n" + " and llamas (for wool), and pigs (for milk and meat). All other unnamed tame units will be marked\n" + " for slaughter as soon as they arrive in your fortress.\n"; namespace DFHack { DBG_DECLARE(autobutcher, status); diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index b1932b269..f61f9d635 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -37,7 +37,6 @@ static const string autonestbox_help = "If the pen is bigger than 1x1 the nestbox must be in the top left corner.\n" "Only 1 unit will be assigned per pen, regardless of the size.\n" "The age of the units is currently not checked, most birds grow up quite fast.\n" - "When called without options autonestbox will instantly run once.\n" "Usage:\n" "\n" "enable autonestbox\n" diff --git a/plugins/lua/autobutcher.lua b/plugins/lua/autobutcher.lua index ab334896c..f357f8fb1 100644 --- a/plugins/lua/autobutcher.lua +++ b/plugins/lua/autobutcher.lua @@ -1,14 +1,5 @@ local _ENV = mkmodule('plugins.autobutcher') ---[[ - - Native functions: - - * autobutcher_isEnabled() - * autowatch_isEnabled() - ---]] - local argparse = require('argparse') local function is_int(val) From db81538f63c3a4a6c334d084dccd311077e50885 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 02:00:15 -0700 Subject: [PATCH 422/854] update changelog --- docs/changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index ce61d31d3..efaab734d 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -34,6 +34,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## New Plugins +- `autonestbox`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autonestbox start`` to `enable autonestbox`. +- `autobutcher`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autobutcher start`` to `enable autobutcher`. ## New Tweaks From 4acb59cb64666f0ce3c57c56b3f3de667a433a15 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Aug 2022 09:01:43 +0000 Subject: [PATCH 423/854] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- plugins/autonestbox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index f61f9d635..10c0e3cd8 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -100,7 +100,7 @@ static void autonestbox_cycle(color_ostream &out); static void init_autonestbox(color_ostream &out) { config = World::GetPersistentData(CONFIG_KEY); - + if (!config.isValid()) config = World::AddPersistentData(CONFIG_KEY); From 9595e2152da4e9502b9926894b7af9540041d13e Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 02:02:23 -0700 Subject: [PATCH 424/854] update changelog (fix typo) --- docs/changelog.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index efaab734d..2733cd539 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -34,8 +34,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## New Plugins -- `autonestbox`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autonestbox start`` to `enable autonestbox`. -- `autobutcher`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autobutcher start`` to `enable autobutcher`. +- `autonestbox`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autonestbox start`` to ``enable autonestbox``. +- `autobutcher`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autobutcher start`` to ``enable autobutcher``. ## New Tweaks From f98015ae5544b526aab2799796894bd1535f42c4 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 10:42:54 -0700 Subject: [PATCH 425/854] ensure we run every N ticks, not frames add more debug messages fix watching/unwatching/forgetting races that aren't in the watchlist --- data/examples/init/onMapLoad_dreamfort.init | 10 ++- plugins/autobutcher.cpp | 67 +++++++++++++++------ plugins/autonestbox.cpp | 14 +++-- 3 files changed, 61 insertions(+), 30 deletions(-) diff --git a/data/examples/init/onMapLoad_dreamfort.init b/data/examples/init/onMapLoad_dreamfort.init index c75620846..05d32134f 100644 --- a/data/examples/init/onMapLoad_dreamfort.init +++ b/data/examples/init/onMapLoad_dreamfort.init @@ -37,17 +37,14 @@ enable automelt # creates manager orders to produce replacements for worn clothing enable tailor -tailor enable # auto-assigns nesting birds to nestbox zones and protects fertile eggs from # being cooked/eaten -enable zone nestboxes -autonestbox start +enable zone autonestbox nestboxes # manages seed stocks enable seedwatch seedwatch all 30 -seedwatch start # ensures important tasks get assigned to workers. # otherwise these job types can get ignored in busy forts. @@ -65,6 +62,7 @@ prioritize -a --reaction-name=TAN_A_HIDE CustomReaction # feel free to change this to "target 0 0 0 0" if you don't expect to want to raise # any animals not listed here -- you can always change it anytime during the game # later if you change your mind. +on-new-fortress enable autobutcher on-new-fortress autobutcher target 2 2 2 2 new # dogs and cats. You should raise the limits for dogs if you will be training them # for hunting or war. @@ -82,5 +80,5 @@ on-new-fortress autobutcher target 2 2 4 2 ALPACA SHEEP LLAMA on-new-fortress autobutcher target 5 5 6 2 PIG # butcher all unprofitable animals on-new-fortress autobutcher target 0 0 0 0 HORSE YAK DONKEY WATER_BUFFALO GOAT CAVY BIRD_DUCK BIRD_GUINEAFOWL -# start it up! -on-new-fortress autobutcher start; autobutcher watch all; autobutcher autowatch +# watch for new animals +on-new-fortress autobutcher autowatch diff --git a/plugins/autobutcher.cpp b/plugins/autobutcher.cpp index 69799207a..0b0d4ffdb 100644 --- a/plugins/autobutcher.cpp +++ b/plugins/autobutcher.cpp @@ -150,7 +150,7 @@ static void set_config_bool(int index, bool value) { } static unordered_map race_to_id; -static size_t cycle_counter = 0; // how many ticks since the last cycle +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle static size_t DEFAULT_CYCLE_TICKS = 6000; @@ -252,7 +252,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan } DFhackCExport command_result plugin_onupdate(color_ostream &out) { - if (is_enabled && ++cycle_counter >= (size_t)get_config_val(CONFIG_CYCLE_TICKS)) + if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) autobutcher_cycle(out); return CR_OK; } @@ -663,7 +663,8 @@ static void autobutcher_status(color_ostream &out) { static void autobutcher_target(color_ostream &out, const autobutcher_options &opts) { if (opts.races_new) { - DEBUG(status,out).print("setting targets for new races\n"); + DEBUG(status,out).print("setting targets for new races to fk=%u, mk=%u, fa=%u, ma=%u\n", + opts.fk, opts.mk, opts.fa, opts.ma); set_config_val(CONFIG_DEFAULT_FK, opts.fk); set_config_val(CONFIG_DEFAULT_MK, opts.mk); set_config_val(CONFIG_DEFAULT_FA, opts.fa); @@ -671,7 +672,8 @@ static void autobutcher_target(color_ostream &out, const autobutcher_options &op } if (opts.races_all) { - DEBUG(status,out).print("setting targets for all races on watchlist\n"); + DEBUG(status,out).print("setting targets for all races on watchlist to fk=%u, mk=%u, fa=%u, ma=%u\n", + opts.fk, opts.mk, opts.fa, opts.ma); for (auto w : watched_races) { w.second->fk = opts.fk; w.second->mk = opts.mk; @@ -689,7 +691,6 @@ static void autobutcher_target(color_ostream &out, const autobutcher_options &op int id = race_to_id[*race]; WatchedRace *w; if (!watched_races.count(id)) { - DEBUG(status,out).print("adding new targets for %s\n", race->c_str()); w = new WatchedRace(out, id, true, opts.fk, opts.mk, opts.fa, opts.ma); watched_races.emplace(id, w); } else { @@ -707,8 +708,6 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o unordered_set ids; if (opts.races_all) { - DEBUG(status,out).print("modifying all races on watchlist: %s\n", - opts.command.c_str()); for (auto w : watched_races) ids.emplace(w.first); } @@ -722,13 +721,41 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o } for (int id : ids) { - if (opts.command == "watch") - watched_races[id]->isWatched = true; - else if (opts.command == "unwatch") - watched_races[id]->isWatched = false; + if (opts.command == "watch") { + if (!watched_races.count(id)) { + watched_races.emplace(id, + new WatchedRace(out, id, true, + get_config_val(CONFIG_DEFAULT_FK), + get_config_val(CONFIG_DEFAULT_MK), + get_config_val(CONFIG_DEFAULT_FA), + get_config_val(CONFIG_DEFAULT_MA))); + } + else if (!watched_races[id]->isWatched) { + DEBUG(status,out).print("watching: %s\n", opts.command.c_str()); + watched_races[id]->isWatched = true; + } + } + else if (opts.command == "unwatch") { + if (!watched_races.count(id)) { + watched_races.emplace(id, + new WatchedRace(out, id, false, + get_config_val(CONFIG_DEFAULT_FK), + get_config_val(CONFIG_DEFAULT_MK), + get_config_val(CONFIG_DEFAULT_FA), + get_config_val(CONFIG_DEFAULT_MA))); + } + else if (watched_races[id]->isWatched) { + DEBUG(status,out).print("unwatching: %s\n", opts.command.c_str()); + watched_races[id]->isWatched = false; + } + } else if (opts.command == "forget") { - watched_races[id]->RemoveConfig(out); - watched_races.erase(id); + if (watched_races.count(id)) { + DEBUG(status,out).print("forgetting: %s\n", opts.command.c_str()); + watched_races[id]->RemoveConfig(out); + delete watched_races[id]; + watched_races.erase(id); + } continue; } watched_races[id]->UpdateConfig(out); @@ -776,7 +803,10 @@ static bool isInBuiltCageRoom(df::unit *unit) { } static void autobutcher_cycle(color_ostream &out) { - DEBUG(cycle,out).print("running autobutcher_cycle\n"); + // mark that we have recently run + cycle_timestamp = world->frame_counter; + + DEBUG(cycle,out).print("running autobutcher cycle\n"); // check if there is anything to watch before walking through units vector if (!get_config_bool(CONFIG_AUTOWATCH)) { @@ -850,14 +880,13 @@ static void autobutcher_cycle(color_ostream &out) { } } - int slaughter_count = 0; for (auto w : watched_races) { - int slaughter_subcount = w.second->ProcessUnits(); - slaughter_count += slaughter_subcount; - if (slaughter_subcount) { + int slaughter_count = w.second->ProcessUnits(); + if (slaughter_count) { stringstream ss; - ss << slaughter_subcount; + ss << slaughter_count; string announce = Units::getRaceNamePluralById(w.first) + " marked for slaughter: " + ss.str(); + DEBUG(cycle,out).print("%s\n", announce.c_str()); Gui::showAnnouncement(announce, 2, false); } } diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index 10c0e3cd8..668938c2c 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -73,7 +73,7 @@ static bool set_config_val(int index, int value) { } static bool did_complain = false; // avoids message spam -static size_t cycle_counter = 0; // how many ticks since the last cycle +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle struct autonestbox_options { // whether to display help @@ -168,8 +168,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan } DFhackCExport command_result plugin_onupdate(color_ostream &out) { - if (is_enabled && ++cycle_counter >= - (size_t)get_config_val(CONFIG_CYCLE_TICKS)) + if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) autonestbox_cycle(out); return CR_OK; } @@ -342,7 +341,7 @@ static bool assignUnitToZone(color_ostream &out, df::unit *unit, df::building *b df::general_ref_building_civzone_assignedst *ref = createCivzoneRef(); if (!ref) { ERR(cycle,out).print("Could not find a clonable activity zone reference!" - " You need to pen/pasture/pit at least one creature" + " You need to manually pen/pasture/pit at least one creature" " before autonestbox can function.\n"); return false; } @@ -381,6 +380,8 @@ static size_t assign_nestboxes(color_ostream &out) { DEBUG(cycle,out).print("Failed to assign unit to building.\n"); return processed; } + DEBUG(cycle,out).print("assigned unit %d to zone %d\n", + free_unit->id, free_building->id); ++processed; } } while (free_unit && free_building); @@ -406,13 +407,16 @@ static size_t assign_nestboxes(color_ostream &out) { static void autonestbox_cycle(color_ostream &out) { // mark that we have recently run - cycle_counter = 0; + cycle_timestamp = world->frame_counter; + + DEBUG(cycle,out).print("running autonestbox cycle\n"); size_t processed = assign_nestboxes(out); if (processed > 0) { stringstream ss; ss << processed << " nestboxes were assigned."; string announce = ss.str(); + DEBUG(cycle,out).print("%s\n", announce.c_str()); Gui::showAnnouncement(announce, 2, false); out << announce << endl; // can complain again From 1dec977476c606ed048289f1d7e8f851d6a16795 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 11:07:36 -0700 Subject: [PATCH 426/854] clean up and add logging to state persistence --- plugins/autonestbox.cpp | 43 +++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index 668938c2c..ea8d7a50d 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -65,11 +65,15 @@ static int get_config_val(int index) { return -1; return config.ival(index); } -static bool set_config_val(int index, int value) { - if (!config.isValid()) - return false; - config.ival(index) = value; - return true; +static bool get_config_bool(int index) { + return get_config_val(index) == 1; +} +static void set_config_val(int index, int value) { + if (config.isValid()) + config.ival(index) = value; +} +static void set_config_bool(int index, bool value) { + set_config_val(index, value ? 1 : 0); } static bool did_complain = false; // avoids message spam @@ -101,23 +105,24 @@ static void autonestbox_cycle(color_ostream &out); static void init_autonestbox(color_ostream &out) { config = World::GetPersistentData(CONFIG_KEY); - if (!config.isValid()) + if (!config.isValid()) { + DEBUG(status,out).print("no config found in this save; initializing\n"); config = World::AddPersistentData(CONFIG_KEY); - - if (get_config_val(CONFIG_IS_ENABLED) == -1) { - set_config_val(CONFIG_IS_ENABLED, 0); + set_config_bool(CONFIG_IS_ENABLED, false); set_config_val(CONFIG_CYCLE_TICKS, 6000); } - if (is_enabled) - set_config_val(CONFIG_IS_ENABLED, 1); - else - is_enabled = (get_config_val(CONFIG_IS_ENABLED) == 1); + is_enabled = get_config_bool(CONFIG_IS_ENABLED); + DEBUG(status,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); did_complain = false; } static void cleanup_autonestbox(color_ostream &out) { - is_enabled = false; + if (is_enabled) { + DEBUG(status,out).print("disabling (not persisting)\n"); + is_enabled = false; + } } DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { @@ -140,10 +145,10 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (enable != is_enabled) { is_enabled = enable; - if (is_enabled) - init_autonestbox(out); - else - cleanup_autonestbox(out); + DEBUG(status,out).print("%s from the API, persisting\n", + is_enabled ? "enabled" : "disabled"); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + init_autonestbox(out); } return CR_OK; } @@ -217,7 +222,7 @@ static command_result df_autonestbox(color_ostream &out, vector ¶met autonestbox_cycle(out); } else { - out << "autonestbox is " << (is_enabled ? "" : "not ") << "running\n"; + out << "autonestbox is " << (is_enabled ? "" : "not ") << "running" << endl; } return CR_OK; } From 1695919411635922a2aece78c8134bc91d7db6c3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 3 Aug 2022 22:40:55 -0700 Subject: [PATCH 427/854] apply canonical class 3 plugin structure --- plugins/autobutcher.cpp | 301 +++++++++++++++++++++------------------- plugins/autonestbox.cpp | 134 +++++++++--------- 2 files changed, 221 insertions(+), 214 deletions(-) diff --git a/plugins/autobutcher.cpp b/plugins/autobutcher.cpp index 0b0d4ffdb..ee93d30e5 100644 --- a/plugins/autobutcher.cpp +++ b/plugins/autobutcher.cpp @@ -1,16 +1,19 @@ -// - full automation of marking live-stock for slaughtering -// races can be added to a watchlist and it can be set how many male/female kids/adults are left alive -// adding to the watchlist can be automated as well. -// config for autobutcher (state and sleep setting) is saved the first time autobutcher is started -// config for watchlist entries is saved when they are created or modified +// full automation of marking live-stock for slaughtering +// races can be added to a watchlist and it can be set how many male/female kids/adults are left alive +// adding to the watchlist can be automated as well. +// config for autobutcher (state and sleep setting) is saved the first time autobutcher is started +// config for watchlist entries is saved when they are created or modified +#include #include #include +#include #include "df/building_cagest.h" #include "df/creature_raw.h" #include "df/world.h" +#include "Core.h" #include "Debug.h" #include "LuaTools.h" #include "PluginManager.h" @@ -33,6 +36,56 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); +// logging levels can be dynamically controlled with the `debugfilter` command. +namespace DFHack { + // for configuration-related logging + DBG_DECLARE(autobutcher, status, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(autobutcher, cycle, DebugCategory::LINFO); +} + +static const string CONFIG_KEY = string(plugin_name) + "/config"; +static const string WATCHLIST_CONFIG_KEY_PREFIX = string(plugin_name) + "/watchlist/"; +static PersistentDataItem config; + +enum ConfigValues { + CONFIG_IS_ENABLED = 0, + CONFIG_CYCLE_TICKS = 1, + CONFIG_AUTOWATCH = 2, + CONFIG_DEFAULT_FK = 3, + CONFIG_DEFAULT_MK = 4, + CONFIG_DEFAULT_FA = 5, + CONFIG_DEFAULT_MA = 6, +}; +static int get_config_val(int index) { + if (!config.isValid()) + return -1; + return config.ival(index); +} +static bool get_config_bool(int index) { + return get_config_val(index) == 1; +} +static void set_config_val(int index, int value) { + if (config.isValid()) + config.ival(index) = value; +} +static void set_config_bool(int index, bool value) { + set_config_val(index, value ? 1 : 0); +} + +struct WatchedRace; +// vector of races handled by autobutcher +// the name is a bit misleading since entries can be set to 'unwatched' +// to ignore them for a while but still keep the target count settings +static unordered_map watched_races; +static unordered_map race_to_id; +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle + +static void init_autobutcher(color_ostream &out); +static void cleanup_autobutcher(color_ostream &out); +static command_result df_autobutcher(color_ostream &out, vector ¶meters); +static void autobutcher_cycle(color_ostream &out); + const string autobutcher_help = "Automatically butcher excess livestock. This plugin monitors how many pets\n" "you have of each gender and age and assigns excess lifestock for slaughter\n" @@ -115,44 +168,90 @@ const string autobutcher_help = " and llamas (for wool), and pigs (for milk and meat). All other unnamed tame units will be marked\n" " for slaughter as soon as they arrive in your fortress.\n"; -namespace DFHack { - DBG_DECLARE(autobutcher, status); - DBG_DECLARE(autobutcher, cycle); +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand( + plugin_name, + "Automatically butcher excess livestock.", + df_autobutcher, + false, + autobutcher_help.c_str())); + return CR_OK; } -static const string CONFIG_KEY = "autobutcher/config"; -static const string WATCHLIST_CONFIG_KEY_PREFIX = "autobutcher/watchlist/"; +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; + } -static PersistentDataItem config; -enum ConfigValues { - CONFIG_IS_ENABLED = 0, - CONFIG_CYCLE_TICKS = 1, - CONFIG_AUTOWATCH = 2, - CONFIG_DEFAULT_FK = 3, - CONFIG_DEFAULT_MK = 4, - CONFIG_DEFAULT_FA = 5, - CONFIG_DEFAULT_MA = 6, -}; -static int get_config_val(int index) { - if (!config.isValid()) - return -1; - return config.ival(index); + if (enable != is_enabled) { + is_enabled = enable; + DEBUG(status,out).print("%s from the API; persisting\n", + is_enabled ? "enabled" : "disabled"); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + } else { + DEBUG(status,out).print("%s from the API, but already %s; no action\n", + is_enabled ? "enabled" : "disabled", + is_enabled ? "enabled" : "disabled"); + } + return CR_OK; } -static bool get_config_bool(int index) { - return get_config_val(index) == 1; + +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + DEBUG(status,out).print("shutting down %s\n", plugin_name); + cleanup_autobutcher(out); + return CR_OK; } -static void set_config_val(int index, int value) { - if (config.isValid()) - config.ival(index) = value; + +DFhackCExport command_result plugin_load_data (color_ostream &out) { + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) { + DEBUG(status,out).print("no config found in this save; initializing\n"); + config = World::AddPersistentData(CONFIG_KEY); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + set_config_val(CONFIG_CYCLE_TICKS, 6000); + set_config_bool(CONFIG_AUTOWATCH, false); + set_config_val(CONFIG_DEFAULT_FK, 5); + set_config_val(CONFIG_DEFAULT_MK, 1); + set_config_val(CONFIG_DEFAULT_FA, 5); + set_config_val(CONFIG_DEFAULT_MA, 1); + } + + // we have to copy our enabled flag into the global plugin variable, but + // all the other state we can directly read/modify from the persistent + // data structure. + is_enabled = get_config_bool(CONFIG_IS_ENABLED); + DEBUG(status,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); + + // load the persisted watchlist + init_autobutcher(out); + + return CR_OK; } -static void set_config_bool(int index, bool value) { - set_config_val(index, value ? 1 : 0); + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (event == DFHack::SC_WORLD_UNLOADED) { + if (is_enabled) { + DEBUG(status,out).print("world unloaded; disabling %s\n", + plugin_name); + is_enabled = false; + } + cleanup_autobutcher(out); + } + return CR_OK; } -static unordered_map race_to_id; -static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) + autobutcher_cycle(out); + return CR_OK; +} -static size_t DEFAULT_CYCLE_TICKS = 6000; +///////////////////////////////////////////////////// +// autobutcher config logic +// struct autobutcher_options { // whether to display help @@ -199,68 +298,30 @@ static const struct_field_info autobutcher_options_fields[] = { }; struct_identity autobutcher_options::_identity(sizeof(autobutcher_options), &df::allocator_fn, NULL, "autobutcher_options", NULL, autobutcher_options_fields); -static void init_autobutcher(color_ostream &out); -static void cleanup_autobutcher(color_ostream &out); -static command_result df_autobutcher(color_ostream &out, vector ¶meters); -static void autobutcher_cycle(color_ostream &out); - -DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand( - "autobutcher", - "Automatically butcher excess livestock.", - df_autobutcher, - false, - autobutcher_help.c_str())); - - init_autobutcher(out); - return CR_OK; -} - -DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { - if (!Maps::IsValid()) { - out.printerr("Cannot run autobutcher without a loaded map.\n"); - return CR_FAILURE; - } +static bool get_options(color_ostream &out, + autobutcher_options &opts, + const vector ¶meters) +{ + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); - if (enable != is_enabled) { - is_enabled = enable; - if (is_enabled) - init_autobutcher(out); - else - cleanup_autobutcher(out); + if (!lua_checkstack(L, parameters.size() + 2) || + !Lua::PushModulePublic( + out, L, "plugins.autobutcher", "parse_commandline")) { + out.printerr("Failed to load autobutcher Lua code\n"); + return false; } - return CR_OK; -} -DFhackCExport command_result plugin_shutdown (color_ostream &out) { - cleanup_autobutcher(out); - return CR_OK; -} + Lua::Push(L, &opts); + for (const string ¶m : parameters) + Lua::Push(L, param); -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { - switch (event) { - case DFHack::SC_MAP_LOADED: - init_autobutcher(out); - break; - case DFHack::SC_MAP_UNLOADED: - cleanup_autobutcher(out); - break; - default: - break; - } - return CR_OK; -} + if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) + return false; -DFhackCExport command_result plugin_onupdate(color_ostream &out) { - if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) - autobutcher_cycle(out); - return CR_OK; + return true; } -///////////////////////////////////////////////////// -// autobutcher config logic -// - static void doMarkForSlaughter(df::unit *unit) { unit->flags2.bits.slaughter = 1; } @@ -460,35 +521,7 @@ public: } }; -// vector of races handled by autobutcher -// the name is a bit misleading since entries can be set to 'unwatched' -// to ignore them for a while but still keep the target count settings -static unordered_map watched_races; - static void init_autobutcher(color_ostream &out) { - config = World::GetPersistentData(CONFIG_KEY); - - if (!config.isValid()) - config = World::AddPersistentData(CONFIG_KEY); - - if (get_config_val(CONFIG_IS_ENABLED) == -1) { - set_config_bool(CONFIG_IS_ENABLED, false); - set_config_val(CONFIG_CYCLE_TICKS, DEFAULT_CYCLE_TICKS); - set_config_bool(CONFIG_AUTOWATCH, false); - set_config_val(CONFIG_DEFAULT_FK, 5); - set_config_val(CONFIG_DEFAULT_MK, 1); - set_config_val(CONFIG_DEFAULT_FA, 5); - set_config_val(CONFIG_DEFAULT_MA, 1); - } - - if (is_enabled) - set_config_bool(CONFIG_IS_ENABLED, true); - else - is_enabled = (get_config_val(CONFIG_IS_ENABLED) == 1); - - if (!config.isValid()) - return; - if (!race_to_id.size()) { const size_t num_races = world->raws.creatures.all.size(); for(size_t i = 0; i < num_races; ++i) @@ -505,37 +538,13 @@ static void init_autobutcher(color_ostream &out) { } static void cleanup_autobutcher(color_ostream &out) { - is_enabled = false; + DEBUG(status,out).print("cleaning %s state\n", plugin_name); race_to_id.clear(); for (auto w : watched_races) delete w.second; watched_races.clear(); } -static bool get_options(color_ostream &out, - autobutcher_options &opts, - const vector ¶meters) -{ - auto L = Lua::Core::State; - Lua::StackUnwinder top(L); - - if (!lua_checkstack(L, parameters.size() + 2) || - !Lua::PushModulePublic( - out, L, "plugins.autobutcher", "parse_commandline")) { - out.printerr("Failed to load autobutcher Lua code\n"); - return false; - } - - Lua::Push(L, &opts); - for (const string ¶m : parameters) - Lua::Push(L, param); - - if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) - return false; - - return true; -} - static void autobutcher_export(color_ostream &out); static void autobutcher_status(color_ostream &out); static void autobutcher_target(color_ostream &out, const autobutcher_options &opts); @@ -544,8 +553,8 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o static command_result df_autobutcher(color_ostream &out, vector ¶meters) { CoreSuspender suspend; - if (!Maps::IsValid()) { - out.printerr("Cannot run autobutcher without a loaded map.\n"); + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot run %s without a loaded world.\n", plugin_name); return CR_FAILURE; } @@ -763,7 +772,7 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o } ///////////////////////////////////////////////////// -// autobutcher cycle logic +// cycle logic // // check if contained in item (e.g. animals in cages) @@ -806,7 +815,7 @@ static void autobutcher_cycle(color_ostream &out) { // mark that we have recently run cycle_timestamp = world->frame_counter; - DEBUG(cycle,out).print("running autobutcher cycle\n"); + DEBUG(cycle,out).print("running %s cycle\n", plugin_name); // check if there is anything to watch before walking through units vector if (!get_config_bool(CONFIG_AUTOWATCH)) { diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index ea8d7a50d..c47f4425f 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -4,6 +4,9 @@ // maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds // state and sleep setting is saved the first time autonestbox is started (to avoid writing stuff if the plugin is never used) +#include +#include + #include "df/building_cagest.h" #include "df/building_civzonest.h" #include "df/building_nest_boxst.h" @@ -16,7 +19,6 @@ #include "modules/Buildings.h" #include "modules/Gui.h" -#include "modules/Maps.h" #include "modules/Persistence.h" #include "modules/Units.h" #include "modules/World.h" @@ -50,11 +52,13 @@ static const string autonestbox_help = " The default is 6000 (about 8 days)\n"; namespace DFHack { - DBG_DECLARE(autonestbox, status); - DBG_DECLARE(autonestbox, cycle); + // for configuration-related logging + DBG_DECLARE(autonestbox, status, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(autonestbox, cycle, DebugCategory::LINFO); } -static const string CONFIG_KEY = "autonestbox/config"; +static const string CONFIG_KEY = string(plugin_name) + "/config"; static PersistentDataItem config; enum ConfigValues { CONFIG_IS_ENABLED = 0, @@ -79,95 +83,65 @@ static void set_config_bool(int index, bool value) { static bool did_complain = false; // avoids message spam static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle -struct autonestbox_options { - // whether to display help - bool help = false; - - // whether to run a cycle right now - bool now = false; - - // how many ticks to wait between automatic cycles, -1 means unset - int32_t ticks = -1; - - static struct_identity _identity; -}; -static const struct_field_info autonestbox_options_fields[] = { - { struct_field_info::PRIMITIVE, "help", offsetof(autonestbox_options, help), &df::identity_traits::identity, 0, 0 }, - { struct_field_info::PRIMITIVE, "now", offsetof(autonestbox_options, now), &df::identity_traits::identity, 0, 0 }, - { struct_field_info::PRIMITIVE, "ticks", offsetof(autonestbox_options, ticks), &df::identity_traits::identity, 0, 0 }, - { struct_field_info::END } -}; -struct_identity autonestbox_options::_identity(sizeof(autonestbox_options), &df::allocator_fn, NULL, "autonestbox_options", NULL, autonestbox_options_fields); - static command_result df_autonestbox(color_ostream &out, vector ¶meters); static void autonestbox_cycle(color_ostream &out); -static void init_autonestbox(color_ostream &out) { - config = World::GetPersistentData(CONFIG_KEY); - - if (!config.isValid()) { - DEBUG(status,out).print("no config found in this save; initializing\n"); - config = World::AddPersistentData(CONFIG_KEY); - set_config_bool(CONFIG_IS_ENABLED, false); - set_config_val(CONFIG_CYCLE_TICKS, 6000); - } - - is_enabled = get_config_bool(CONFIG_IS_ENABLED); - DEBUG(status,out).print("loading persisted enabled state: %s\n", - is_enabled ? "true" : "false"); - did_complain = false; -} - -static void cleanup_autonestbox(color_ostream &out) { - if (is_enabled) { - DEBUG(status,out).print("disabling (not persisting)\n"); - is_enabled = false; - } -} - DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "autonestbox", + plugin_name, "Auto-assign egg-laying female pets to nestbox zones.", df_autonestbox, false, autonestbox_help.c_str())); - - init_autonestbox(out); return CR_OK; } DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { - if (!Maps::IsValid()) { - out.printerr("Cannot run autonestbox without a loaded map.\n"); + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot enable %s without a loaded world.\n", plugin_name); return CR_FAILURE; } if (enable != is_enabled) { is_enabled = enable; - DEBUG(status,out).print("%s from the API, persisting\n", + DEBUG(status,out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled"); set_config_bool(CONFIG_IS_ENABLED, is_enabled); - init_autonestbox(out); + } else { + DEBUG(status,out).print("%s from the API, but already %s; no action\n", + is_enabled ? "enabled" : "disabled", + is_enabled ? "enabled" : "disabled"); } return CR_OK; } -DFhackCExport command_result plugin_shutdown (color_ostream &out) { - cleanup_autonestbox(out); +DFhackCExport command_result plugin_load_data (color_ostream &out) { + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) { + DEBUG(status,out).print("no config found in this save; initializing\n"); + config = World::AddPersistentData(CONFIG_KEY); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + set_config_val(CONFIG_CYCLE_TICKS, 6000); + } + + // we have to copy our enabled flag into the global plugin variable, but + // all the other state we can directly read/modify from the persistent + // data structure. + is_enabled = get_config_bool(CONFIG_IS_ENABLED); + DEBUG(status,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); + did_complain = false; return CR_OK; } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { - switch (event) { - case DFHack::SC_MAP_LOADED: - init_autonestbox(out); - break; - case DFHack::SC_MAP_UNLOADED: - cleanup_autonestbox(out); - break; - default: - break; + if (event == DFHack::SC_WORLD_UNLOADED) { + if (is_enabled) { + DEBUG(status,out).print("world unloaded; disabling %s\n", + plugin_name); + is_enabled = false; + } } return CR_OK; } @@ -178,6 +152,30 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { return CR_OK; } +///////////////////////////////////////////////////// +// configuration interface +// + +struct autonestbox_options { + // whether to display help + bool help = false; + + // whether to run a cycle right now + bool now = false; + + // how many ticks to wait between automatic cycles, -1 means unset + int32_t ticks = -1; + + static struct_identity _identity; +}; +static const struct_field_info autonestbox_options_fields[] = { + { struct_field_info::PRIMITIVE, "help", offsetof(autonestbox_options, help), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "now", offsetof(autonestbox_options, now), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ticks", offsetof(autonestbox_options, ticks), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::END } +}; +struct_identity autonestbox_options::_identity(sizeof(autonestbox_options), &df::allocator_fn, NULL, "autonestbox_options", NULL, autonestbox_options_fields); + static bool get_options(color_ostream &out, autonestbox_options &opts, const vector ¶meters) @@ -205,8 +203,8 @@ static bool get_options(color_ostream &out, static command_result df_autonestbox(color_ostream &out, vector ¶meters) { CoreSuspender suspend; - if (!Maps::IsValid()) { - out.printerr("Cannot run autonestbox without a loaded map.\n"); + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot run %s without a loaded world.\n", plugin_name); return CR_FAILURE; } @@ -228,7 +226,7 @@ static command_result df_autonestbox(color_ostream &out, vector ¶met } ///////////////////////////////////////////////////// -// autonestbox cycle logic +// cycle logic // static bool isEmptyPasture(df::building *building) { From 3b17448597f8c0233f6d59082629de497fbf5799 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 3 Aug 2022 23:34:56 -0700 Subject: [PATCH 428/854] update docs for autobutcher --- docs/plugins/autobutcher.rst | 175 +++++++++++++++++++++++++++++++++++ plugins/autobutcher.cpp | 86 +---------------- 2 files changed, 176 insertions(+), 85 deletions(-) create mode 100644 docs/plugins/autobutcher.rst diff --git a/docs/plugins/autobutcher.rst b/docs/plugins/autobutcher.rst new file mode 100644 index 000000000..e77e13232 --- /dev/null +++ b/docs/plugins/autobutcher.rst @@ -0,0 +1,175 @@ +autobutcher +=========== +Tags: +:dfhack-keybind:`autobutcher` + +Automatically butcher excess livestock. This plugin monitors how many pets you +have of each gender and age and assigns excess lifestock for slaughter. Requires +that you add the target race(s) to a watch list. Units will be ignored if they +are: + +* Untamed +* Nicknamed (for custom protection; you can use the `rename` ``unit`` tool + individually, or `zone` ``nick`` for groups) +* Caged, if and only if the cage is defined as a room (to protect zoos) +* Trained for war or hunting + +Creatures who will not reproduce (because they're not interested in the +opposite sex or have been gelded) will be butchered before those who will. +Older adults and younger children will be butchered first if the population +is above the target (defaults are: 1 male kid, 5 female kids, 1 male adult, +5 female adults). Note that you may need to set a target above 1 to have a +reliable breeding population due to asexuality etc. See `fix-ster` if this is a +problem. + +Usage: + +``enable autobutcher`` + Start processing livestock according to the configuration. Note that + no races are watched by default. You have to add the ones you want to + monitor with ``autobutcher watch``, ``autobutcher target`` or + ``autobutcher autowatch``. +``autobutcher autowatch`` + Automatically add all new races (animals you buy from merchants, tame + yourself, or get from migrants) to the watch list using the default target + counts. +``autobutcher noautowatch`` + Stop auto-adding new races to the watch list. +``autobutcher target all|new| [ ...]`` + Set target counts for the specified races: + fk = number of female kids + mk = number of male kids + fa = number of female adults + ma = number of female adults + If you specify ``all``, then this command will set the counts for all races + on your current watchlist (including the races which are currenly set to + 'unwatched') and sets the new default for future watch commands. If you + specify ``new``, then this command just sets the new default counts for + future watch commands without changing your current watchlist. Otherwise, + all space separated races listed will be modified (or added to the watchlist + if they aren't there already). +``autobutcher ticks `` + Change the number of ticks between scanning cycles when the plugin is + enabled. By default, a cycle happens every 6000 ticks (about 8 game days). +``autobutcher watch all| [ ...]`` + Start watching the listed races. If they aren't already in your watchlist, + then they will be added with the default target counts. If you specify the + keyword ``all``, then all races in your watchlist that are currently marked + as unwatched will become watched. +``autobutcher unwatch all| [ ...]`` + Stop watching the specified race(s) (or all races on your watchlist if + ``all`` is given). The current target settings will be remembered. +``autobutcher forget all| [ ...]`` + Unwatch the specified race(s) (or all races on your watchlist if ``all`` is + given) and forget target settings for it/them. +``autobutcher [list]`` + Print status and current settings, including the watchlist. This is the + default command if autobutcher is run without parameters. +``autobutcher list_export`` + Print commands required to set the current settings in another fort. + Useful to run form dfhack-run like: + ``dfhack-run autobutcher list_export > autobutcher.script`` + +To see a list of all races, run this command: + + devel/query --table df.global.world.raws.creatures.all --search ^creature_id --maxdepth 1 + +Though not all the races listed there are tameable/butcherable. + +Examples +-------- + +Keep at most 7 kids (4 female, 3 male) and at most 3 adults (2 female, 1 male) +for turkeys. Once the kids grow up, the oldest adults will get slaughtered. +Excess kids will get slaughtered starting the the youngest to allow that the +older ones grow into adults:: + + autobutcher target 4 3 2 1 BIRD_TURKEY + +Configure useful limits for dogs, cats, geese (for eggs, leather, and bones), +alpacas, sheep, and llamas (for wool), and pigs (for milk and meat). All other +unnamed tame units will be marked for slaughter as soon as they arrive in your +fortress:: + + autobutcher target 2 2 2 2 DOG + autobutcher target 1 1 2 2 CAT + autobutcher target 50 50 14 2 BIRD_GOOSE + autobutcher target 2 2 4 2 ALPACA SHEEP LLAMA + autobutcher target 5 5 6 2 PIG + autobutcher target 0 0 0 0 new + autobutcher autowatch + +Options: + +:example: Print some usage examples. +:start: Start running every X frames (df simulation ticks). + Default: X=6000, which would be every 60 seconds at 100fps. +:stop: Stop running automatically. +:sleep : Changes the timer to sleep X frames between runs. +:watch R: Start watching a race. R can be a valid race RAW id (ALPACA, + BIRD_TURKEY, etc) or a list of ids seperated by spaces or + the keyword 'all' which affects all races on your current + watchlist. +:unwatch R: Stop watching race(s). The current target settings will be + remembered. R can be a list of ids or the keyword 'all'. +:forget R: Stop watching race(s) and forget it's/their target settings. + R can be a list of ids or the keyword 'all'. +:autowatch: Automatically adds all new races (animals you buy from merchants, + tame yourself or get from migrants) to the watch list using + default target count. +:noautowatch: Stop auto-adding new races to the watchlist. +:list: Print the current status and watchlist. +:list_export: Print the commands needed to set up status and watchlist, + which can be used to import them to another save (see notes). +:target : + Set target count for specified race(s). The first four arguments + are the number of female and male kids, and female and male adults. + R can be a list of spceies ids, or the keyword ``all`` or ``new``. + ``R = 'all'``: change target count for all races on watchlist + and set the new default for the future. ``R = 'new'``: don't touch + current settings on the watchlist, only set the new default + for future entries. +:list_export: Print the commands required to rebuild your current settings. + +.. note:: + + Settings and watchlist are stored in the savegame, so that you can have + different settings for each save. If you want to copy your watchlist to + another savegame you must export the commands required to recreate your settings. + + To export, open an external terminal in the DF directory, and run + ``dfhack-run autobutcher list_export > filename.txt``. To import, load your + new save and run ``script filename.txt`` in the DFHack terminal. + + +Examples: + +You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, +1 male) of the race alpaca. Once the kids grow up the oldest adults will get +slaughtered. Excess kids will get slaughtered starting with the youngest +to allow that the older ones grow into adults. Any unnamed cats will +be slaughtered as soon as possible. :: + + autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY + autobutcher target 0 0 0 0 CAT + autobutcher watch ALPACA BIRD_TURKEY CAT + autobutcher start + +Automatically put all new races onto the watchlist and mark unnamed tame units +for slaughter as soon as they arrive in your fort. Settings already made +for specific races will be left untouched. :: + + autobutcher target 0 0 0 0 new + autobutcher autowatch + autobutcher start + +Stop watching the races alpaca and cat, but remember the target count +settings so that you can use 'unwatch' without the need to enter the +values again. Note: 'autobutcher unwatch all' works, but only makes sense +if you want to keep the plugin running with the 'autowatch' feature or manually +add some new races with 'watch'. If you simply want to stop it completely use +'autobutcher stop' instead. :: + + autobutcher unwatch ALPACA CAT + +const string autobutcher_help = diff --git a/plugins/autobutcher.cpp b/plugins/autobutcher.cpp index ee93d30e5..32c8b8764 100644 --- a/plugins/autobutcher.cpp +++ b/plugins/autobutcher.cpp @@ -86,95 +86,11 @@ static void cleanup_autobutcher(color_ostream &out); static command_result df_autobutcher(color_ostream &out, vector ¶meters); static void autobutcher_cycle(color_ostream &out); -const string autobutcher_help = - "Automatically butcher excess livestock. This plugin monitors how many pets\n" - "you have of each gender and age and assigns excess lifestock for slaughter\n" - "once they reach a specific count. Requires\n" - "that you add the target race(s) to a watch list. Only tame units will be\n" - "processed. Named units will be completely ignored (you can give animals\n" - "nicknames with the tool 'rename unit' to protect them from getting slaughtered\n" - "automatically). Trained war or hunting pets will be ignored.\n" - "Once you have too many adults, the oldest will be butchered first.\n" - "Once you have too many kids, the youngest will be butchered first.\n" - "If you don't set a target count the following default will be used:\n" - "1 male kid, 5 female kids, 1 male adult, 5 female adults.\n" - "\n" - "Usage:\n" - "\n" - "enable autobutcher\n" - " Start processing livestock according to the configuration. Note that\n" - " no races are watched by default. You have to add the ones you want to\n" - " monitor (or use autowatch)\n" - "autobutcher autowatch\n" - " Automatically add all new races (animals you buy\n" - " from merchants, tame yourself, or get from migrants)\n" - " to the watch list using the default target counts.\n" - "autobutcher noautowatch\n" - " Stop auto-adding new races to the watch list.\n" - "autobutcher target all|new| [ ...] \n" - " Set target counts for the specified races:\n" - " fk = number of female kids\n" - " mk = number of male kids\n" - " fa = number of female adults\n" - " ma = number of female adults\n" - " If you specify 'all', then this command will set the counts for all races\n" - " on your current watchlist (including the races which are currenly set to\n" - " 'unwatched') and sets the new default for future watch commands. If you\n" - " specify 'new', then this command just sets the new default counts for\n" - " future watch commands without changing your current watchlist. Otherwise,\n" - " all space separated races listed will be modified (or added to the watchlist\n" - " if they aren't there already).\n" - "autobutcher ticks \n" - " Change the number of ticks between scanning cycles when the plugin is\n" - " enabled. By default, a cycle happens every 6000 ticks (about 8 game days).\n" - "autobutcher watch all| [ ...]\n" - " Start watching the listed races. If they aren't already in your watchlist, then\n" - " they will be added with the default target counts. If you specify the keyword 'all',\n" - " then all races in your watchlist that are currently marked as unwatched will become\n" - " watched.\n" - "autobutcher unwatch all| [ ...]\n" - " Stop watching the specified race(s) (or all races on your watchlist if 'all' is\n" - " given). The current target settings will be remembered.\n" - "autobutcher forget all| [ ...]\n" - " Unwatch the specified race(s) (or all races on your watchlist if 'all' is given)\n" - " and forget target settings for it/them.\n" - "autobutcher [list]\n" - " Print status and current settings, including the watchlist.\n" - "autobutcher list_export\n" - " Print commands required to set the current settings in another fort.\n" - " Useful to run form dfhack-run like: 'dfhack-run autobutcher list_export > autobutcher.script'\n" - "\n" - "To see a list of all races, run this command:\n" - "\n" - " devel/query --table df.global.world.raws.creatures.all --search ^creature_id --maxdepth 1'\n" - "\n" - "Though not all the races listed there are tameable/butcherable\n" - "\n" - "Examples:\n" - "\n" - "autobutcher target 4 3 2 1 BIRD_TURKEY\n" - " This means you want to have at most 7 kids (4 female, 3 male) and at most 3 adults\n" - " (2 female, 1 male) for turkeys. Once the kids grow up, the\n" - " oldest adults will get slaughtered. Excess kids will get slaughtered starting\n" - " the the youngest to allow that the older ones grow into adults.\n" - "autobutcher target 2 2 2 2 DOG\n" - "autobutcher target 1 1 2 2 CAT\n" - "autobutcher target 50 50 14 2 BIRD_GOOSE\n" - "autobutcher target 2 2 4 2 ALPACA SHEEP LLAMA\n" - "autobutcher target 5 5 6 2 PIG\n" - "autobutcher target 0 0 0 0 new\n" - "autobutcher autowatch\n" - " Configure useful limits for dogs, cats, geese (for eggs, leather, and bones), alpacas, sheep,\n" - " and llamas (for wool), and pigs (for milk and meat). All other unnamed tame units will be marked\n" - " for slaughter as soon as they arrive in your fortress.\n"; - DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( plugin_name, "Automatically butcher excess livestock.", - df_autobutcher, - false, - autobutcher_help.c_str())); + df_autobutcher)); return CR_OK; } From b2ab93b3cd97448ad5705590676c837e3d27b1a1 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 3 Aug 2022 23:35:13 -0700 Subject: [PATCH 429/854] update docs for autonestbox --- docs/plugins/autonestbox.rst | 27 +++++++++++++++++++++++++++ plugins/autonestbox.cpp | 22 +--------------------- 2 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 docs/plugins/autonestbox.rst diff --git a/docs/plugins/autonestbox.rst b/docs/plugins/autonestbox.rst new file mode 100644 index 000000000..6842bef5c --- /dev/null +++ b/docs/plugins/autonestbox.rst @@ -0,0 +1,27 @@ +autonestbox +=========== +Tags: +:dfhack-keybind:`autonestbox` + +Auto-assign egg-laying female pets to nestbox zones. Requires that you create +pen/pasture zones above nestboxes. If the pen is bigger than 1x1, the nestbox +must be in the top left corner. Only 1 unit will be assigned per pen, regardless +of the size. The age of the units is currently not checked since most birds grow +up quite fast. Egg layers who are also grazers will be ignored, since confining +them to a 1x1 pasture is not a good idea. Only tame and domesticated own units +are processed since pasturing half-trained wild egg layers could destroy your +neat nestbox zones when they revert to wild. + +Usage: + +``enable autonestbox`` + Start checking for unpastured egg-layers and assigning them to nestbox + zones. +``autonestbox`` + Print current status. +``autonestbox now`` + Run a scan and assignment cycle right now. Does not require that the plugin + is enabled. +``autonestbox ticks `` + Change the number of ticks between scan and assignment cycles when the + plugin is enabled. The default is 6000 (about 8 days). diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index c47f4425f..dd052f234 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -33,24 +33,6 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); -static const string autonestbox_help = - "Assigns unpastured female egg-layers to nestbox zones.\n" - "Requires that you create pen/pasture zones above nestboxes.\n" - "If the pen is bigger than 1x1 the nestbox must be in the top left corner.\n" - "Only 1 unit will be assigned per pen, regardless of the size.\n" - "The age of the units is currently not checked, most birds grow up quite fast.\n" - "Usage:\n" - "\n" - "enable autonestbox\n" - " Start checking for unpastured egg-layers and assigning them to nestbox zones.\n" - "autonestbox\n" - " Print current status." - "autonestbox now\n" - " Run a scan and assignment cycle right now. Does not require that the plugin is enabled.\n" - "autonestbox ticks \n" - " Change the number of ticks between scan and assignment cycles when the plugin is enabled.\n" - " The default is 6000 (about 8 days)\n"; - namespace DFHack { // for configuration-related logging DBG_DECLARE(autonestbox, status, DebugCategory::LINFO); @@ -90,9 +72,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Wed, 3 Aug 2022 23:40:35 -0700 Subject: [PATCH 430/854] update docs for workflow --- docs/plugins/workflow.rst | 153 +++++++++++++++++++++----------------- plugins/workflow.cpp | 81 ++------------------ 2 files changed, 91 insertions(+), 143 deletions(-) diff --git a/docs/plugins/workflow.rst b/docs/plugins/workflow.rst index f6b8c4475..cb20ad806 100644 --- a/docs/plugins/workflow.rst +++ b/docs/plugins/workflow.rst @@ -1,73 +1,69 @@ workflow ======== -Manage control of repeat jobs. `gui/workflow` provides a simple +Tags: +:dfhack-keybind:`workflow` +:dfhack-keybind:`fix-job-postings` + +Manage repeat jobs according to stock levels. `gui/workflow` provides a simple front-end integrated in the game UI. -Usage: +When the plugin is enabled, it protects all repeat jobs from removal. If they do +disappear due to any cause (raw materials not available, manual removal by the +player, etc.), they are immediately re-added to their workshop and suspended. + +If any constraints on item amounts are set, repeat jobs that produce that kind +of item are automatically suspended and resumed as the item amount goes above or +below the limit. -``workflow enable [option...], workflow disable [option...]`` - If no options are specified, enables or disables the plugin. - Otherwise, enables or disables any of the following options: +There is a good amount of overlap between this plugin and the vanilla manager +workorders, and both systems have their advantages. Vanilla manager workorders +can be more expressive about when to enqueue jobs. For example, you can gate the +activation of a vanilla workorder based on availability of raw materials, which +you cannot do in ``workflow``. However, ``workflow`` is often more convenient +for quickly keeping a small stock of various items on hand without having to +configure all the vanilla manager options. Also see the `orders` plugin for +a library of manager orders that may make managing your stocks even more +convenient than ``workflow`` can. - - drybuckets: Automatically empty abandoned water buckets. - - auto-melt: Resume melt jobs when there are objects to melt. +Usage: + +``enable workflow`` + Start monitoring for and managing workshop jobs that are set to repeat. +``workflow enable|disable drybuckets`` + Enables/disables automatic emptying of abandoned water buckets. +``workflow enable|disable auto-melt`` + Enables/disables automatic resumption of repeat melt jobs when there are + objects to melt. +``workflow count [gap]`` + Set a constraint, counting every stack as 1 item. If a gap is specified, + stocks are allowed to dip that many items below the target before relevant + jobs are resumed. +``workflow amount [gap]`` + Set a constraint, counting all items within stacks. If a gap is specified, + stocks are allowed to dip that many items below the target before relevant + jobs are resumed. +``workflow unlimit `` + Delete a constraint. +``workflow unlimit-all`` + Delete all constraints. ``workflow jobs`` - List workflow-controlled jobs (if in a workshop, filtered by it). + List workflow-controlled jobs (if in a workshop, filtered by it). ``workflow list`` - List active constraints, and their job counts. + List active constraints, and their job counts. ``workflow list-commands`` - List active constraints as workflow commands that re-create them; - this list can be copied to a file, and then reloaded using the - ``script`` built-in command. -``workflow count [cnt-gap]`` - Set a constraint, counting every stack as 1 item. -``workflow amount [cnt-gap]`` - Set a constraint, counting all items within stacks. -``workflow unlimit `` - Delete a constraint. -``workflow unlimit-all`` - Delete all constraints. - -Function + List active constraints as workflow commands that re-create them; this list + can be copied to a file, and then reloaded using the `script` built-in + command. +``fix-job-postings [dry-run]`` + Fixes crashes caused the version of workflow released with DFHack + 0.40.24-r4. It will be run automatically if needed. If your save has never + been run with this version, you will never need this command. Specify the + ``dry-run`` keyword to see what this command would do without making any + changes to game state. + +Examples -------- -When the plugin is enabled, it protects all repeat jobs from removal. -If they do disappear due to any cause, they are immediately re-added to their -workshop and suspended. - -In addition, when any constraints on item amounts are set, repeat jobs that -produce that kind of item are automatically suspended and resumed as the item -amount goes above or below the limit. The gap specifies how much below the limit -the amount has to drop before jobs are resumed; this is intended to reduce -the frequency of jobs being toggled. - -Constraint format ------------------ -The constraint spec consists of 4 parts, separated with ``/`` characters:: - - ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,] - -The first part is mandatory and specifies the item type and subtype, -using the raw tokens for items (the same syntax used custom reaction inputs). -For more information, see :wiki:`this wiki page `. - -The subsequent parts are optional: - -- A generic material spec constrains the item material to one of - the hard-coded generic classes, which currently include:: - PLANT WOOD CLOTH SILK LEATHER BONE SHELL SOAP TOOTH HORN PEARL YARN - METAL STONE SAND GLASS CLAY MILK - -- A specific material spec chooses the material exactly, using the - raw syntax for reaction input materials, e.g. ``INORGANIC:IRON``, - although for convenience it also allows just ``IRON``, or ``ACACIA:WOOD`` etc. - See the link above for more details on the unabbreviated raw syntax. - -- A comma-separated list of miscellaneous flags, which currently can - be used to ignore imported items or items below a certain quality. - -Constraint examples -------------------- Keep metal bolts within 900-1000, and wood/bone within 150-200:: workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100 @@ -104,16 +100,39 @@ Make sure there are always 80-100 units of dimple dye:: .. note:: - In order for this to work, you have to set the material of the PLANT input - on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the `job item-material ` - command. Otherwise the plugin won't be able to deduce the output material. + In order for this to work, you have to set the material of the PLANT input + on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the + `job item-material ` command. Otherwise the plugin won't be able to + deduce the output material. Maintain 10-100 locally-made crafts of exceptional quality:: workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90 -fix-job-postings ----------------- -This command fixes crashes caused by previous versions of workflow, mostly in -DFHack 0.40.24-r4, and should be run automatically when loading a world (but can -also be run manually if desired). +Constraint format +----------------- + +The constraint spec consists of 4 parts, separated with ``/`` characters:: + + ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,] + +The first part is mandatory and specifies the item type and subtype, using the +raw tokens for items (the same syntax used custom reaction inputs). For more +information, see :wiki:`this wiki page `. + +The subsequent parts are optional: + +- A generic material spec constrains the item material to one of the hard-coded + generic classes, which currently include:: + + PLANT WOOD CLOTH SILK LEATHER BONE SHELL SOAP TOOTH HORN PEARL YARN + METAL STONE SAND GLASS CLAY MILK + +- A specific material spec chooses the material exactly, using the raw syntax + for reaction input materials, e.g. ``INORGANIC:IRON``, although for + convenience it also allows just ``IRON``, or ``ACACIA:WOOD`` etc. See the + link above for more details on the unabbreviated raw syntax. + +- A comma-separated list of miscellaneous flags, which currently can be used to + ignore imported items (``LOCAL``) or items below a certain quality (1-5, with + 5 being masterwork). diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 600a8bef1..83f312bfd 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -73,84 +73,13 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector [cnt-gap]\n" - " workflow amount [cnt-gap]\n" - " Set a constraint. The first form counts each stack as only 1 item.\n" - " workflow unlimit \n" - " Delete a constraint.\n" - " workflow unlimit-all\n" - " Delete all constraints.\n" - "Function:\n" - " - When the plugin is enabled, it protects all repeat jobs from removal.\n" - " If they do disappear due to any cause, they are immediately re-added\n" - " to their workshop and suspended.\n" - " - In addition, when any constraints on item amounts are set, repeat jobs\n" - " that produce that kind of item are automatically suspended and resumed\n" - " as the item amount goes above or below the limit. The gap specifies how\n" - " much below the limit the amount has to drop before jobs are resumed;\n" - " this is intended to reduce the frequency of jobs being toggled.\n" - "Constraint format:\n" - " The contstraint spec consists of 4 parts, separated with '/' characters:\n" - " ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,]\n" - " The first part is mandatory and specifies the item type and subtype,\n" - " using the raw tokens for items, in the same syntax you would e.g. use\n" - " for a custom reaction input. The subsequent parts are optional:\n" - " - A generic material spec constrains the item material to one of\n" - " the hard-coded generic classes, like WOOD, METAL, YARN or MILK.\n" - " - A specific material spec chooses the material exactly, using the\n" - " raw syntax for reaction input materials, e.g. INORGANIC:IRON,\n" - " although for convenience it also allows just IRON, or ACACIA:WOOD.\n" - " - A comma-separated list of miscellaneous flags, which currently can\n" - " be used to ignore imported items or items below a certain quality.\n" - "Constraint examples:\n" - " workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100\n" - " workflow amount AMMO:ITEM_AMMO_BOLTS/WOOD,BONE 200 50\n" - " Keep metal bolts within 900-1000, and wood/bone within 150-200.\n" - " workflow count FOOD 120 30\n" - " workflow count DRINK 120 30\n" - " Keep the number of prepared food & drink stacks between 90 and 120\n" - " workflow count BIN 30\n" - " workflow count BARREL 30\n" - " workflow count BOX/CLOTH,SILK,YARN 30\n" - " Make sure there are always 25-30 empty bins/barrels/bags.\n" - " workflow count BAR//COAL 20\n" - " workflow count BAR//COPPER 30\n" - " Make sure there are always 15-20 coal and 25-30 copper bars.\n" - " workflow count CRAFTS//GOLD 20\n" - " Produce 15-20 gold crafts.\n" - " workflow count POWDER_MISC/SAND 20\n" - " workflow count BOULDER/CLAY 20\n" - " Collect 15-20 sand bags and clay boulders.\n" - " workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20\n" - " Make sure there are always 80-100 units of dimple dye.\n" - " In order for this to work, you have to set the material of\n" - " the PLANT input on the Mill Plants job to MUSHROOM_CUP_DIMPLE\n" - " using the 'job item-material' command.\n" - " workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90\n" - " Maintain 10-100 locally-made crafts of exceptional quality.\n" - ) - ); + "workflow", + "Manage repeat jobs according to stock levels.", + workflow_cmd)); commands.push_back(PluginCommand( "fix-job-postings", - "Fix broken job postings caused by certain versions of workflow", - fix_job_postings_cmd, false, - "fix-job-postings: Fix job postings\n" - "fix-job-postings dry|[any argument]: Dry run only (avoid making changes)\n" - )); + "Fix broken job postings caused by very old versions of workflow.", + fix_job_postings_cmd)); } init_state(out); From 26863fe4686bdfc2942ce9b7ed59cb73d1c5dc46 Mon Sep 17 00:00:00 2001 From: Myk Date: Thu, 4 Aug 2022 00:18:06 -0700 Subject: [PATCH 431/854] Update zone.rst --- docs/plugins/zone.rst | 115 ------------------------------------------ 1 file changed, 115 deletions(-) diff --git a/docs/plugins/zone.rst b/docs/plugins/zone.rst index be827b47c..a3112c147 100644 --- a/docs/plugins/zone.rst +++ b/docs/plugins/zone.rst @@ -1,6 +1,3 @@ -.. _autobutcher: -.. _autonestbox: - zone ==== Helps a bit with managing activity zones (pens, pastures and pits) and cages. @@ -131,115 +128,3 @@ Examples ``zone tocages count 50 own tame male not grazer`` Stuff up to 50 owned tame male animals who are not grazers into cages built on the current default zone. - -autobutcher -=========== -Assigns lifestock for slaughter once it reaches a specific count. Requires that -you add the target race(s) to a watch list. Only tame units will be processed. - -Units will be ignored if they are: - -* Nicknamed (for custom protection; you can use the `rename` ``unit`` tool - individually, or `zone` ``nick`` for groups) -* Caged, if and only if the cage is defined as a room (to protect zoos) -* Trained for war or hunting - -Creatures who will not reproduce (because they're not interested in the -opposite sex or have been gelded) will be butchered before those who will. -Older adults and younger children will be butchered first if the population -is above the target (default 1 male, 5 female kids and adults). Note that -you may need to set a target above 1 to have a reliable breeding population -due to asexuality etc. See `fix-ster` if this is a problem. - -Options: - -:example: Print some usage examples. -:start: Start running every X frames (df simulation ticks). - Default: X=6000, which would be every 60 seconds at 100fps. -:stop: Stop running automatically. -:sleep : Changes the timer to sleep X frames between runs. -:watch R: Start watching a race. R can be a valid race RAW id (ALPACA, - BIRD_TURKEY, etc) or a list of ids seperated by spaces or - the keyword 'all' which affects all races on your current - watchlist. -:unwatch R: Stop watching race(s). The current target settings will be - remembered. R can be a list of ids or the keyword 'all'. -:forget R: Stop watching race(s) and forget it's/their target settings. - R can be a list of ids or the keyword 'all'. -:autowatch: Automatically adds all new races (animals you buy from merchants, - tame yourself or get from migrants) to the watch list using - default target count. -:noautowatch: Stop auto-adding new races to the watchlist. -:list: Print the current status and watchlist. -:list_export: Print the commands needed to set up status and watchlist, - which can be used to import them to another save (see notes). -:target : - Set target count for specified race(s). The first four arguments - are the number of female and male kids, and female and male adults. - R can be a list of spceies ids, or the keyword ``all`` or ``new``. - ``R = 'all'``: change target count for all races on watchlist - and set the new default for the future. ``R = 'new'``: don't touch - current settings on the watchlist, only set the new default - for future entries. -:list_export: Print the commands required to rebuild your current settings. - -.. note:: - - Settings and watchlist are stored in the savegame, so that you can have - different settings for each save. If you want to copy your watchlist to - another savegame you must export the commands required to recreate your settings. - - To export, open an external terminal in the DF directory, and run - ``dfhack-run autobutcher list_export > filename.txt``. To import, load your - new save and run ``script filename.txt`` in the DFHack terminal. - - -Examples: - -You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, -1 male) of the race alpaca. Once the kids grow up the oldest adults will get -slaughtered. Excess kids will get slaughtered starting with the youngest -to allow that the older ones grow into adults. Any unnamed cats will -be slaughtered as soon as possible. :: - - autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY - autobutcher target 0 0 0 0 CAT - autobutcher watch ALPACA BIRD_TURKEY CAT - autobutcher start - -Automatically put all new races onto the watchlist and mark unnamed tame units -for slaughter as soon as they arrive in your fort. Settings already made -for specific races will be left untouched. :: - - autobutcher target 0 0 0 0 new - autobutcher autowatch - autobutcher start - -Stop watching the races alpaca and cat, but remember the target count -settings so that you can use 'unwatch' without the need to enter the -values again. Note: 'autobutcher unwatch all' works, but only makes sense -if you want to keep the plugin running with the 'autowatch' feature or manually -add some new races with 'watch'. If you simply want to stop it completely use -'autobutcher stop' instead. :: - - autobutcher unwatch ALPACA CAT - -autonestbox -=========== -Assigns unpastured female egg-layers to nestbox zones. Requires that you create -pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox -must be in the top left corner. Only 1 unit will be assigned per pen, regardless -of the size. The age of the units is currently not checked, most birds grow up -quite fast. Egglayers who are also grazers will be ignored, since confining them -to a 1x1 pasture is not a good idea. Only tame and domesticated own units are -processed since pasturing half-trained wild egglayers could destroy your neat -nestbox zones when they revert to wild. When called without options autonestbox -will instantly run once. - -Options: - -:start: Start running every X frames (df simulation ticks). - Default: X=6000, which would be every 60 seconds at 100fps. -:stop: Stop running automatically. -:sleep: Must be followed by number X. Changes the timer to sleep X - frames between runs. From 5e9dde8a61a8a409c56210106ad3617b7ccfe477 Mon Sep 17 00:00:00 2001 From: Myk Date: Thu, 4 Aug 2022 00:22:07 -0700 Subject: [PATCH 432/854] Update autobutcher.rst --- docs/plugins/autobutcher.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/plugins/autobutcher.rst b/docs/plugins/autobutcher.rst index e77e13232..6bd9a02ce 100644 --- a/docs/plugins/autobutcher.rst +++ b/docs/plugins/autobutcher.rst @@ -37,10 +37,10 @@ Usage: Stop auto-adding new races to the watch list. ``autobutcher target all|new| [ ...]`` Set target counts for the specified races: - fk = number of female kids - mk = number of male kids - fa = number of female adults - ma = number of female adults + - fk = number of female kids + - mk = number of male kids + - fa = number of female adults + - ma = number of female adults If you specify ``all``, then this command will set the counts for all races on your current watchlist (including the races which are currenly set to 'unwatched') and sets the new default for future watch commands. If you @@ -171,5 +171,3 @@ add some new races with 'watch'. If you simply want to stop it completely use 'autobutcher stop' instead. :: autobutcher unwatch ALPACA CAT - -const string autobutcher_help = From 8f242ac5cc391fd6be141b45ea8c2640b9bc39cc Mon Sep 17 00:00:00 2001 From: Myk Date: Thu, 4 Aug 2022 00:24:36 -0700 Subject: [PATCH 433/854] Update tweak.rst --- docs/plugins/tweak.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/tweak.rst b/docs/plugins/tweak.rst index 56d465f78..fff1a4512 100644 --- a/docs/plugins/tweak.rst +++ b/docs/plugins/tweak.rst @@ -62,7 +62,7 @@ Commands that persist until disabled or DF quits: Adds an option to butcher units when viewing cages with :kbd:`q`. ``civ-view-agreement`` Fixes overlapping text on the "view agreement" screen. -``condition-material`` +``condition-material`` Fixes a crash in the work order contition material list (:bug:`9905`). ``craft-age-wear`` Fixes crafted items not wearing out over time (:bug:`6003`). With this From feed91d098354d7963f065195e328ed89af80549 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 4 Aug 2022 07:34:32 +0000 Subject: [PATCH 434/854] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/plugins/petcapRemover.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/plugins/petcapRemover.rst b/docs/plugins/petcapRemover.rst index e3ce47916..3d9739ea3 100644 --- a/docs/plugins/petcapRemover.rst +++ b/docs/plugins/petcapRemover.rst @@ -29,4 +29,3 @@ Usage: ``petcapRemover pregtime `` Sets the pregnancy duration to the specified number of ticks. The default value is 200,000 ticks, which is the natural pet pregnancy duration. - From 46c3862a082c5d02013deed45db17902d4b24905 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 5 Aug 2022 08:08:34 -0700 Subject: [PATCH 435/854] update docs for zone --- docs/plugins/zone.rst | 254 +++++++++++++++++++++++------------------- plugins/zone.cpp | 140 ++--------------------- 2 files changed, 150 insertions(+), 244 deletions(-) diff --git a/docs/plugins/zone.rst b/docs/plugins/zone.rst index a3112c147..5e01a12ac 100644 --- a/docs/plugins/zone.rst +++ b/docs/plugins/zone.rst @@ -1,130 +1,158 @@ zone ==== -Helps a bit with managing activity zones (pens, pastures and pits) and cages. - +Tags: :dfhack-keybind:`zone` -Options: - -:set: Set zone or cage under cursor as default for future assigns. -:assign: Assign unit(s) to the pen or pit marked with the 'set' command. - If no filters are set a unit must be selected in the in-game ui. - Can also be followed by a valid zone id which will be set - instead. -:unassign: Unassign selected creature from it's zone. -:nick: Mass-assign nicknames, must be followed by the name you want - to set. -:remnick: Mass-remove nicknames. -:enumnick: Assign enumerated nicknames (e.g. "Hen 1", "Hen 2"...). Must be - followed by the prefix to use in nicknames. -:tocages: Assign unit(s) to cages inside a pasture. -:uinfo: Print info about unit(s). If no filters are set a unit must - be selected in the in-game ui. -:zinfo: Print info about zone(s). If no filters are set zones under - the cursor are listed. -:verbose: Print some more info. -:filters: Print list of valid filter options. -:examples: Print some usage examples. -:not: Negates the next filter keyword. - -Filters: - -:all: Process all units (to be used with additional filters). -:count: Must be followed by a number. Process only n units (to be used - with additional filters). -:unassigned: Not assigned to zone, chain or built cage. -:minage: Minimum age. Must be followed by number. -:maxage: Maximum age. Must be followed by number. -:race: Must be followed by a race RAW ID (e.g. BIRD_TURKEY, ALPACA, - etc). Negatable. -:caged: In a built cage. Negatable. -:own: From own civilization. Negatable. -:merchant: Is a merchant / belongs to a merchant. Should only be used for - pitting, not for stealing animals (slaughter should work). -:war: Trained war creature. Negatable. -:hunting: Trained hunting creature. Negatable. -:tamed: Creature is tame. Negatable. -:trained: Creature is trained. Finds war/hunting creatures as well as - creatures who have a training level greater than 'domesticated'. - If you want to specifically search for war/hunting creatures use - 'war' or 'hunting' Negatable. -:trainablewar: Creature can be trained for war (and is not already trained for - war/hunt). Negatable. -:trainablehunt: Creature can be trained for hunting (and is not already trained - for war/hunt). Negatable. -:male: Creature is male. Negatable. -:female: Creature is female. Negatable. -:egglayer: Race lays eggs. Negatable. -:grazer: Race is a grazer. Negatable. -:milkable: Race is milkable. Negatable. +Manage activity zones, cages, and the animals therein. + +Usage: + +``enable zone`` + Add helpful filters to the pen/pasture sidebar menu (e.g. show only caged + grazers). +``zone set`` + Set zone or cage under cursor as default for future ``assign`` or ``tocages`` + commands. +``zone assign [] []`` + Assign unit(s) to the zone with the given ID, or to the most recent pen or + pit marked with the ``set`` command. If no filters are set, then a unit must + be selected in the in-game ui. +``zone unassign []`` + Unassign selected creature from its zone. +``zone nick []`` + Assign the given nickname to the selected animal or the animals matched by + the given filter. +``zone remnick []`` + Remove nicknames from the selected animal or the animals matched by the + given filter. +``zone enumnick []`` + Assign enumerated nicknames (e.g. "Hen 1", "Hen 2"...). +``zone tocages []`` + Assign unit(s) to cages that have been built inside the pasture selected + with the ``set`` command. +``zone uinfo []`` + Print info about unit(s). If no filters are set, then a unit must be + selected in the in-game ui. +``zone zinfo`` + Print info about the zone(s) and any buildings under the cursor. + +Examples +-------- + +Before any ``assign`` or ``tocages`` examples can be used, you must first move +the cursor over a pen/pasture or pit zone and run ``zone set`` to select the +zone. + +``zone assign all own ALPACA minage 3 maxage 10`` + Assign all of your alpacas who are between 3 and 10 years old to the + selected pasture. +``zone assign all own caged grazer nick ineedgrass`` + Assign all of your grazers who are sitting in cages on stockpiles (e.g. + after buying them from merchants) to the selected pasture and give them the + nickname 'ineedgrass'. +``zone assign all own not grazer not race CAT`` + Assign all of your animals who are not grazers (excluding cats) to the + selected pasture. + " zone assign all own milkable not grazer\n" +``zone assign all own female milkable not grazer`` + Assign all of your non-grazing milkable creatures to the selected pasture or + cage. +``zone assign all own race DWARF maxage 2`` + Throw all useless kids into a pit :) They'll be fine I'm sure. +``zone nick donttouchme`` + Nicknames all units in the current default zone or cage to 'donttouchme'. + This is especially useful for protecting a group of animals assigned to a + pasture or cage from being "processed" by `autobutcher`. +``zone tocages count 50 own tame male not grazer`` + Stuff up to 50 of your tame male animals who are not grazers into cages + built on the current default zone. + +Filters +------- + +:all: Process all units. +:count : Process only up to n units. +:unassigned: Not assigned to zone, chain or built cage. +:minage : Minimum age. Must be followed by number. +:maxage : Maximum age. Must be followed by number. +:not: Negates the next filter keyword. All of the keywords documented + below are negatable. +:race: Must be followed by a race RAW ID (e.g. BIRD_TURKEY, ALPACA, + etc). +:caged: In a built cage. +:own: From own civilization. You'll usually want to include this + filter. +:war: Trained war creature. +:hunting: Trained hunting creature. +:tamed: Creature is tame. +:trained: Creature is trained. Finds war/hunting creatures as well as + creatures who have a training level greater than 'domesticated'. + If you want to specifically search for war/hunting creatures + use ``war`` or ``hunting``. +:trainablewar: Creature can be trained for war (and is not already trained for + war/hunt). +:trainablehunt: Creature can be trained for hunting (and is not already trained + for war/hunt). +:male: Creature is male. +:female: Creature is female. +:egglayer: Race lays eggs. If you want units who actually lay eggs, also + specify ``female``. +:grazer: Race is a grazer. +:milkable: Race is milkable. If you want units who actually can be milked, + also specify ``female``. +:merchant: Is a merchant / belongs to a merchant. Should only be used for + pitting or slaughtering, not for stealing animals. Usage with single units ----------------------- -One convenient way to use the zone tool is to bind the command 'zone assign' to -a hotkey, maybe also the command 'zone set'. Place the in-game cursor over -a pen/pasture or pit, use 'zone set' to mark it. Then you can select units -on the map (in 'v' or 'k' mode), in the unit list or from inside cages -and use 'zone assign' to assign them to their new home. Allows pitting your -own dwarves, by the way. - -Usage with filters ------------------- -All filters can be used together with the 'assign' command. - -Restrictions: It's not possible to assign units who are inside built cages -or chained because in most cases that won't be desirable anyways. -It's not possible to cage owned pets because in that case the owner +One convenient way to use the zone tool is to bind the commands ``zone assign`` +and ``zone set`` to hotkeys. Place the in-game cursor over a pen/pasture or pit +and use the ``zone set`` hotkey to mark it. Then you can select units on the map +(in 'v' or 'k' mode), in the unit list or from inside cages and use the +``zone assign`` hotkey to assign them to their new home. Allows pitting your own +dwarves, by the way. + +Matching with filters +--------------------- +All filters can be used together with the ``assign`` and ``tocages`` commands. + +Note that it's not possible to reassign units who are inside built cages or +chained, though this likely won't matter because if you have gone to the trouble +of creating a zoo or chaining a creature, you probably wouldn't want them +reassigned anyways. Also, ``zone`` will avoid caging owned pets because the owner uncages them after a while which results in infinite hauling back and forth. -Usually you should always use the filter 'own' (which implies tame) unless you -want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless -you specify 'race DWARF' (so it's safe to use 'assign all own' to one big -pasture if you want to have all your animals at the same place). 'egglayer' and -'milkable' should be used together with 'female' unless you have a mod with -egg-laying male elves who give milk or whatever. Merchants and their animals are -ignored unless you specify 'merchant' (pitting them should be no problem, -but stealing and pasturing their animals is not a good idea since currently they -are not properly added to your own stocks; slaughtering them should work). +Most filters should include an ``own`` element (which implies ``tame``) unless +you want to use ``zone assign`` for pitting hostiles. The ``own`` filter ignores +dwarves unless you explicitly specify ``race DWARF`` (so it's safe to use +``assign all own`` to one big pasture if you want to have all your animals in +the same place). -Most filters can be negated (e.g. 'not grazer' -> race is not a grazer). +The ``egglayer`` and ``milkable`` filters should be used together with +``female`` unless you want the males of the race included. Merchants and their +animals are ignored unless you specify ``merchant`` (pitting them should be no +problem, but stealing and pasturing their animals is not a good idea since +currently they are not properly added to your own stocks; slaughtering them +should work). + +Most filters can be negated (e.g. ``not grazer`` -> race is not a grazer). Mass-renaming ------------- -Using the 'nick' command you can set the same nickname for multiple units. -If used without 'assign', 'all' or 'count' it will rename all units in the -current default target zone. Combined with 'assign', 'all' or 'count' (and -further optional filters) it will rename units matching the filter conditions. + +Using the ``nick`` command, you can set the same nickname for multiple units. +If used without ``assign``, ``all``, or ``count``, it will rename all units in +the current default target zone. Combined with ``assign``, ``all``, or ``count`` +(and likely further optional filters) it will rename units matching the filter +conditions. Cage zones ---------- -Using the 'tocages' command you can assign units to a set of cages, for example -a room next to your butcher shop(s). They will be spread evenly among available -cages to optimize hauling to and butchering from them. For this to work you need -to build cages and then place one pen/pasture activity zone above them, covering -all cages you want to use. Then use 'zone set' (like with 'assign') and use -'zone tocages filter1 filter2 ...'. 'tocages' overwrites 'assign' because it -would make no sense, but can be used together with 'nick' or 'remnick' and all -the usual filters. -Examples --------- -``zone assign all own ALPACA minage 3 maxage 10`` - Assign all own alpacas who are between 3 and 10 years old to the selected - pasture. -``zone assign all own caged grazer nick ineedgrass`` - Assign all own grazers who are sitting in cages on stockpiles (e.g. after - buying them from merchants) to the selected pasture and give them - the nickname 'ineedgrass'. -``zone assign all own not grazer not race CAT`` - Assign all own animals who are not grazers, excluding cats. -``zone assign count 5 own female milkable`` - Assign up to 5 own female milkable creatures to the selected pasture. -``zone assign all own race DWARF maxage 2`` - Throw all useless kids into a pit :) -``zone nick donttouchme`` - Nicknames all units in the current default zone or cage to 'donttouchme'. - Mostly intended to be used for special pastures or cages which are not marked - as rooms you want to protect from autobutcher. -``zone tocages count 50 own tame male not grazer`` - Stuff up to 50 owned tame male animals who are not grazers into cages built - on the current default zone. +The ``tocages`` command assigns units to a set of cages, for example a room next +to your butcher shop(s). Units will be spread evenly among available cages to +optimize hauling to and butchering from them. For this to work you need to build +cages and then place one pen/pasture activity zone above them, covering all +cages you want to use. Then use ``zone set`` (like with ``assign``) and run +``zone tocages ``. ``tocages`` can be used together with ``nick`` or +``remnick`` to adjust nicknames while assigning to cages. diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 60f1f2b61..a3a0f2431 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -66,105 +66,6 @@ REQUIRE_GLOBAL(ui_building_in_assign); REQUIRE_GLOBAL(ui_menu_width); REQUIRE_GLOBAL(world); -static command_result df_zone (color_ostream &out, vector & parameters); - -const string zone_help = - "Allows easier management of pens/pastures, pits and cages.\n" - "Commands:\n" - " help - print this help message\n" - " filters - print list of supported filters\n" - " examples - print some usage examples\n" - " set - set zone under cursor as default for future assigns\n" - " assign - assign creature(s) to a pen or pit\n" - " if no filters are used, a single unit must be selected.\n" - " can be followed by valid building id which will then be set.\n" - " building must be a pen/pasture, pit or cage.\n" - " slaughter - mark creature(s) for slaughter\n" - " if no filters are used, a single unit must be selected.\n" - " with filters named units are ignored unless specified.\n" - " unassign - unassign selected creature(s) from zone or cage\n" - " nick - give unit(s) nicknames (e.g. all units in a cage)\n" - " enumnick - give unit(s) enumerated nicknames (e.g Hen 1, Hen 2)\n" - " remnick - remove nicknames\n" - " tocages - assign to (multiple) built cages inside a pen/pasture\n" - " spreads creatures evenly among cages for faster hauling.\n" - " uinfo - print info about selected units\n" - " zinfo - print info about zone(s) under cursor\n" - "Options:\n" - " verbose - print some more info, mostly useless debug stuff\n" - ; - -const string zone_help_filters = - "Filters (to be used in combination with 'all' or 'count'):\n" - "Required (one of):\n" - " all - process all units\n" - " should be used in combination with further filters\n" - " count - must be followed by number. process X units\n" - " should be used in combination with further filters\n" - "Others (may be used with 'not' prefix):\n" - " age - exact age. must be followed by number\n" - " caged - in a built cage\n" - " egglayer - race lays eggs (use together with 'female')\n" - " female - obvious\n" - " grazer - is a grazer\n" - " male - obvious\n" - " maxage - maximum age. must be followed by number\n" - " merchant - is a merchant / belongs to a merchant\n" - " can be used to pit merchants and slaughter their animals\n" - " (could have weird effects during trading, be careful)\n" - " ('not merchant' is set by default)\n" - " milkable - race is milkable (use together with 'female')\n" - " minage - minimum age. must be followed by number\n" - " named - has name or nickname\n" - " ('not named' is set by default when using the 'slaughter' command)\n" - " own - from own civilization\n" - " race - must be followed by a race raw id (e.g. BIRD_TURKEY)\n" - " tame - tamed\n" - " trainablehunt- can be trained for hunting (and is not already trained)\n" - " trainablewar - can be trained for war (and is not already trained)\n" - " trained - obvious\n" - " unassigned - not assigned to zone, chain or built cage\n" - " war - trained war creature\n" - ; - -const string zone_help_examples = - "Example for assigning single units:\n" - " (ingame) move cursor to a pen/pasture or pit zone\n" - " (dfhack) 'zone set' to use this zone for future assignments\n" - " (dfhack) map 'zone assign' to a hotkey of your choice\n" - " (ingame) select unit with 'v', 'k' or from unit list or inside a cage\n" - " (ingame) press hotkey to assign unit to it's new home (or pit)\n" - "Examples for assigning with filters:\n" - " (this assumes you have already set up a target zone)\n" - " zone assign all own grazer maxage 10\n" - " zone assign all own milkable not grazer\n" - " zone assign count 5 own female milkable\n" - " zone assign all own race DWARF maxage 2\n" - " throw all useless kids into a pit :)\n" - "Notes:\n" - " Unassigning per filters ignores built cages and chains currently. Usually you\n" - " should always use the filter 'own' (which implies tame) unless you want to\n" - " use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you\n" - " specify 'race DWARF' and it ignores merchants and their animals unless you\n" - " specify 'merchant' (so it's safe to use 'assign all own' to one big pasture\n" - " if you want to have all your animals at the same place).\n" - " 'egglayer' and 'milkable' should be used together with 'female'\n" - " well, unless you have a mod with egg-laying male elves who give milk...\n"; - - -/////////////// -// Various small tool functions -// probably many of these should be moved to Unit.h and Building.h sometime later... - -// static df::general_ref_building_civzone_assignedst * createCivzoneRef(); -// static bool unassignUnitFromBuilding(df::unit* unit); -// static command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose); -// static void unitInfo(color_ostream & out, df::unit* creature, bool verbose); -// static void zoneInfo(color_ostream & out, df::building* building, bool verbose); -// static void cageInfo(color_ostream & out, df::building* building, bool verbose); -// static void chainInfo(color_ostream & out, df::building* building, bool verbose); -static bool isInBuiltCageRoom(df::unit*); - static void doMarkForSlaughter(df::unit* unit) { unit->flags2.bits.slaughter = 1; @@ -184,6 +85,8 @@ static bool hasValidMapPos(df::unit* unit) return false; } +static bool isInBuiltCageRoom(df::unit*); + // dump some unit info static void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) { @@ -1127,8 +1030,7 @@ static struct zone_param_filters_init { zone_param_filters_init() { zone_param_filters["maxage"] = make_pair(1, createMaxAgeFilter); }} zone_param_filters_init_; -static command_result df_zone (color_ostream &out, vector & parameters) -{ +static command_result df_zone(color_ostream &out, vector & parameters) { CoreSuspender suspend; if (!Maps::IsValid()) @@ -1160,18 +1062,7 @@ static command_result df_zone (color_ostream &out, vector & parameters) if (p0 == "help" || p0 == "?") { - out << zone_help << endl; - return CR_OK; - } - if (p0 == "filters") - { - out << zone_help_filters << endl; - return CR_OK; - } - if (p0 == "examples") - { - out << zone_help_examples << endl; - return CR_OK; + return CR_WRONG_USAGE; } else if(p0 == "zinfo") { @@ -2275,13 +2166,8 @@ IMPLEMENT_VMETHOD_INTERPOSE(zone_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(zone_hook, render); //END zone filters -DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) -{ - if (!gps) - return CR_FAILURE; - - if (enable != is_enabled) - { +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { + if (enable != is_enabled) { if (!INTERPOSE_HOOK(zone_hook, feed).apply(enable) || !INTERPOSE_HOOK(zone_hook, render).apply(enable)) return CR_FAILURE; @@ -2292,18 +2178,10 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) return CR_OK; } -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( "zone", - "manage activity zones.", - df_zone, - false, - zone_help.c_str())); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ + "Manage activity zones.", + df_zone)); return CR_OK; } From 6f48c1f4d063cef6a978b6cece81a7076e530cb7 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 5 Aug 2022 08:08:47 -0700 Subject: [PATCH 436/854] remove some cruft from autobutcher docs --- docs/plugins/autobutcher.rst | 86 +++++------------------------------- 1 file changed, 11 insertions(+), 75 deletions(-) diff --git a/docs/plugins/autobutcher.rst b/docs/plugins/autobutcher.rst index 6bd9a02ce..a6b69338c 100644 --- a/docs/plugins/autobutcher.rst +++ b/docs/plugins/autobutcher.rst @@ -67,8 +67,6 @@ Usage: default command if autobutcher is run without parameters. ``autobutcher list_export`` Print commands required to set the current settings in another fort. - Useful to run form dfhack-run like: - ``dfhack-run autobutcher list_export > autobutcher.script`` To see a list of all races, run this command: @@ -76,6 +74,16 @@ To see a list of all races, run this command: Though not all the races listed there are tameable/butcherable. +.. note:: + + Settings and watchlist are stored in the savegame, so you can have different + settings for each save. If you want to copy your watchlist to another, + savegame, you can export the commands required to recreate your settings. + + To export, open an external terminal in the DF directory, and run + ``dfhack-run autobutcher list_export > filename.txt``. To import, load your + new save and run ``script filename.txt`` in the DFHack terminal. + Examples -------- @@ -91,6 +99,7 @@ alpacas, sheep, and llamas (for wool), and pigs (for milk and meat). All other unnamed tame units will be marked for slaughter as soon as they arrive in your fortress:: + enable autobutcher autobutcher target 2 2 2 2 DOG autobutcher target 1 1 2 2 CAT autobutcher target 50 50 14 2 BIRD_GOOSE @@ -98,76 +107,3 @@ fortress:: autobutcher target 5 5 6 2 PIG autobutcher target 0 0 0 0 new autobutcher autowatch - -Options: - -:example: Print some usage examples. -:start: Start running every X frames (df simulation ticks). - Default: X=6000, which would be every 60 seconds at 100fps. -:stop: Stop running automatically. -:sleep : Changes the timer to sleep X frames between runs. -:watch R: Start watching a race. R can be a valid race RAW id (ALPACA, - BIRD_TURKEY, etc) or a list of ids seperated by spaces or - the keyword 'all' which affects all races on your current - watchlist. -:unwatch R: Stop watching race(s). The current target settings will be - remembered. R can be a list of ids or the keyword 'all'. -:forget R: Stop watching race(s) and forget it's/their target settings. - R can be a list of ids or the keyword 'all'. -:autowatch: Automatically adds all new races (animals you buy from merchants, - tame yourself or get from migrants) to the watch list using - default target count. -:noautowatch: Stop auto-adding new races to the watchlist. -:list: Print the current status and watchlist. -:list_export: Print the commands needed to set up status and watchlist, - which can be used to import them to another save (see notes). -:target : - Set target count for specified race(s). The first four arguments - are the number of female and male kids, and female and male adults. - R can be a list of spceies ids, or the keyword ``all`` or ``new``. - ``R = 'all'``: change target count for all races on watchlist - and set the new default for the future. ``R = 'new'``: don't touch - current settings on the watchlist, only set the new default - for future entries. -:list_export: Print the commands required to rebuild your current settings. - -.. note:: - - Settings and watchlist are stored in the savegame, so that you can have - different settings for each save. If you want to copy your watchlist to - another savegame you must export the commands required to recreate your settings. - - To export, open an external terminal in the DF directory, and run - ``dfhack-run autobutcher list_export > filename.txt``. To import, load your - new save and run ``script filename.txt`` in the DFHack terminal. - - -Examples: - -You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, -1 male) of the race alpaca. Once the kids grow up the oldest adults will get -slaughtered. Excess kids will get slaughtered starting with the youngest -to allow that the older ones grow into adults. Any unnamed cats will -be slaughtered as soon as possible. :: - - autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY - autobutcher target 0 0 0 0 CAT - autobutcher watch ALPACA BIRD_TURKEY CAT - autobutcher start - -Automatically put all new races onto the watchlist and mark unnamed tame units -for slaughter as soon as they arrive in your fort. Settings already made -for specific races will be left untouched. :: - - autobutcher target 0 0 0 0 new - autobutcher autowatch - autobutcher start - -Stop watching the races alpaca and cat, but remember the target count -settings so that you can use 'unwatch' without the need to enter the -values again. Note: 'autobutcher unwatch all' works, but only makes sense -if you want to keep the plugin running with the 'autowatch' feature or manually -add some new races with 'watch'. If you simply want to stop it completely use -'autobutcher stop' instead. :: - - autobutcher unwatch ALPACA CAT From ebfe00b112a69fdea0ea9a50116e5529f1e620db Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 5 Aug 2022 10:08:23 -0700 Subject: [PATCH 437/854] editing pass of short descriptions and fix some short description parsing --- docs/plugins/jobutils.rst | 2 ++ docs/plugins/pathable.rst | 3 ++- docs/plugins/prospector.rst | 4 ++-- docs/plugins/resume.rst | 2 +- docs/plugins/search.rst | 7 ++++--- docs/plugins/xlsxreader.rst | 2 +- library/lua/helpdb.lua | 29 +++++++++++++++++++---------- 7 files changed, 31 insertions(+), 18 deletions(-) diff --git a/docs/plugins/jobutils.rst b/docs/plugins/jobutils.rst index e8434abdc..2ecf2b591 100644 --- a/docs/plugins/jobutils.rst +++ b/docs/plugins/jobutils.rst @@ -7,6 +7,8 @@ Tags: :dfhack-keybind:`job-duplicate` :dfhack-keybind:`job-material` +Inspect or modify details of workshop jobs. + Usage: ``job`` diff --git a/docs/plugins/pathable.rst b/docs/plugins/pathable.rst index daf7697bc..7a906ad89 100644 --- a/docs/plugins/pathable.rst +++ b/docs/plugins/pathable.rst @@ -1,6 +1,7 @@ pathable ======== -Provides a Lua API for marking tiles that are reachable from the cursor. +Marks tiles that are reachable from the cursor. This plugin provides a Lua API, +but no direct commands. See `pathable-api` for details. diff --git a/docs/plugins/prospector.rst b/docs/plugins/prospector.rst index 95436abec..718f92fa6 100644 --- a/docs/plugins/prospector.rst +++ b/docs/plugins/prospector.rst @@ -5,8 +5,8 @@ prospector Tags: :dfhack-keybind:`prospect` -Shows a summary of resources that exist on the map or an estimate of resources -available in the selected embark area. +Shows a summary of resources that exist on the map. It can also calculate an +estimate of resources available in the selected embark area. Usage:: diff --git a/docs/plugins/resume.rst b/docs/plugins/resume.rst index d8feb0896..7afb9be0d 100644 --- a/docs/plugins/resume.rst +++ b/docs/plugins/resume.rst @@ -1,7 +1,7 @@ resume ====== Tags: -:dfhack-keybind:`` +:dfhack-keybind:`resume` Color planned buildings based on their suspend status. When enabled, this plugin will display a colored 'X' over suspended buildings. When run as a command, it diff --git a/docs/plugins/search.rst b/docs/plugins/search.rst index c8f5d68b3..edf6fb47c 100644 --- a/docs/plugins/search.rst +++ b/docs/plugins/search.rst @@ -4,9 +4,10 @@ search ====== Tags: -The search plugin adds search capabilities to the Stocks, Animals, Trading, -Stockpile, Noble (assignment candidates), Military (position candidates), -Burrows (unit list), Rooms, Announcements, Job List, and Unit List screens. +Adds search capabilities to the UI. The Stocks, Animals, Trading, Stockpile, +Noble (assignment candidates), Military (position candidates), Burrows (unit +list), Rooms, Announcements, Job List, and Unit List screens all get hotkeys +that allow you to dynamically filter the displayed lists. Usage:: diff --git a/docs/plugins/xlsxreader.rst b/docs/plugins/xlsxreader.rst index 6429ceedc..d09b69644 100644 --- a/docs/plugins/xlsxreader.rst +++ b/docs/plugins/xlsxreader.rst @@ -1,6 +1,6 @@ xlsxreader ========== -Provides a Lua API for reading .xlsx files. +Provides a Lua API for reading xlsx files. See `xlsxreader-api` for details. diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index cf8e86af5..a23002e57 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -142,11 +142,11 @@ end -- value is the comment character.) local function update_entry(entry, iterator, opts) opts = opts or {} - local lines = {} + local lines, tags = {}, '' local first_line_is_short_help = opts.first_line_is_short_help local begin_marker_found,header_found = not opts.begin_marker,opts.no_header local tags_found, short_help_found = false, opts.skip_short_help - local in_short_help = false + local in_tags, in_keybinding, in_short_help = false, false, false for line in iterator do if not short_help_found and first_line_is_short_help then line = line:trim() @@ -179,16 +179,21 @@ local function update_entry(entry, iterator, opts) end if not header_found and line:find('%w') then header_found = true - elseif not tags_found and line:find('^[*]*Tags:[*]* [%w, ]+$') then - local _,_,tags = line:trim():find('[*]*Tags:[*]* *(.*)') - entry.tags = {} - for _,tag in ipairs(tags:split('[ ,]+')) do - entry.tags[tag] = true + elseif in_tags then + if #line == 0 then + in_tags = false + else + tags = tags .. line end - tags_found = true + elseif not tags_found and line:find('^[*]*Tags:[*]*') then + _,_,tags = line:trim():find('[*]*Tags:[*]* *(.*)') + in_tags, tags_found = true, true + elseif in_keybinding then + if #line == 0 then in_keybinding = false end + elseif line:find('^[*]*Keybinding:') then + in_keybinding = true elseif not short_help_found and - not line:find('^[*]*Keybinding:') and - line:find('%w') then + line:find('^%w') then if in_short_help then entry.short_help = entry.short_help .. ' ' .. line else @@ -205,6 +210,10 @@ local function update_entry(entry, iterator, opts) table.insert(lines, line) ::continue:: end + entry.tags = {} + for _,tag in ipairs(tags:split('[ ,]+')) do + entry.tags[tag] = true + end if #lines > 0 then entry.long_help = table.concat(lines, '\n') end From f7acc5cfc6a28e72fa4ab50e5d63042a5cf96e6c Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 5 Aug 2022 17:55:33 -0700 Subject: [PATCH 438/854] sync tags spreadsheet to git spreadsheet - https://docs.google.com/spreadsheets/d/1hiDlo8M_bB_1jE-5HRs2RrrA_VZ4cRu9VXaTctX_nwk/edit#gid=170388995 sync command - for fname in *rst; do name=$(echo $fname | sed 's/[.]rst//'); tagline=$(egrep ",$name," ~/Downloads/DFHack\ taxonomy\ -\ Tool\ tags.csv | ~/Downloads/csvtotags.sh); sed -ri "s;[*]*Tags:.*;$tagline;" $fname; done contents of csvtotags.sh - fgrep . | sed -r 's/^[^,]+,([^,]+),[^.]+[.]"?,/\1,/' | awk -F, ' function tag(idx, tagname) { if ($idx == "TRUE") { if (hastag == 1) {printf(", ")} printf("`tag/%s`", tagname) hastag = 1 } } { printf("%s", "**Tags:** ") hastag = 0 tag(2, "adventure") tag(3, "fort") tag(4, "legends") tag(5, "embark") tag(6, "system") tag(7, "dev") tag(8, "auto") tag(9, "productivity") tag(10, "inspection") tag(11, "design") tag(12, "quickfort") tag(13, "interface") tag(14, "fps") tag(15, "fix") tag(16, "mod") tag(17, "armok") tag(18, "animals") tag(19, "buildings") tag(20, "items") tag(21, "jobs") tag(22, "map") tag(23, "labors") tag(24, "units") tag(25, "stockpiles") tag(26, "trees") printf("\n") } ' --- docs/Tags.rst | 29 +++++++++++++-------- docs/builtins/alias.rst | 2 +- docs/builtins/cls.rst | 2 +- docs/builtins/die.rst | 2 +- docs/builtins/disable.rst | 2 +- docs/builtins/enable.rst | 2 +- docs/builtins/fpause.rst | 2 +- docs/builtins/help.rst | 2 +- docs/builtins/hide.rst | 2 +- docs/builtins/keybinding.rst | 2 +- docs/builtins/kill-lua.rst | 2 +- docs/builtins/load.rst | 2 +- docs/builtins/ls.rst | 2 +- docs/builtins/plug.rst | 2 +- docs/builtins/reload.rst | 2 +- docs/builtins/sc-script.rst | 2 +- docs/builtins/script.rst | 2 +- docs/builtins/show.rst | 2 +- docs/builtins/tags.rst | 2 +- docs/builtins/type.rst | 2 +- docs/builtins/unload.rst | 2 +- docs/plugins/3dveins.rst | 2 +- docs/plugins/RemoteFortressReader.rst | 2 +- docs/plugins/add-spatter.rst | 2 +- docs/plugins/autobutcher.rst | 2 +- docs/plugins/autochop.rst | 2 +- docs/plugins/autoclothing.rst | 2 +- docs/plugins/autodump.rst | 2 +- docs/plugins/autofarm.rst | 2 +- docs/plugins/autogems.rst | 2 +- docs/plugins/autohauler.rst | 2 +- docs/plugins/autolabor.rst | 2 +- docs/plugins/automaterial.rst | 2 +- docs/plugins/automelt.rst | 2 +- docs/plugins/autonestbox.rst | 2 +- docs/plugins/autotrade.rst | 2 +- docs/plugins/blueprint.rst | 2 +- docs/plugins/building-hacks.rst | 1 + docs/plugins/buildingplan.rst | 2 +- docs/plugins/burrows.rst | 2 +- docs/plugins/changeitem.rst | 2 +- docs/plugins/changelayer.rst | 2 +- docs/plugins/changevein.rst | 2 +- docs/plugins/cleanconst.rst | 2 +- docs/plugins/cleaners.rst | 2 +- docs/plugins/cleanowned.rst | 2 +- docs/plugins/command-prompt.rst | 2 +- docs/plugins/confirm.rst | 2 +- docs/plugins/createitem.rst | 2 +- docs/plugins/cursecheck.rst | 2 +- docs/plugins/cxxrandom.rst | 1 + docs/plugins/debug.rst | 2 +- docs/plugins/deramp.rst | 2 +- docs/plugins/dig-now.rst | 2 +- docs/plugins/dig.rst | 2 +- docs/plugins/digFlood.rst | 2 +- docs/plugins/diggingInvaders.rst | 2 +- docs/plugins/dwarfmonitor.rst | 2 +- docs/plugins/dwarfvet.rst | 2 +- docs/plugins/embark-assistant.rst | 2 +- docs/plugins/embark-tools.rst | 2 +- docs/plugins/eventful.rst | 1 + docs/plugins/fastdwarf.rst | 2 +- docs/plugins/filltraffic.rst | 2 +- docs/plugins/fix-unit-occupancy.rst | 2 +- docs/plugins/fixveins.rst | 2 +- docs/plugins/flows.rst | 2 +- docs/plugins/follow.rst | 2 +- docs/plugins/forceequip.rst | 2 +- docs/plugins/generated-creature-renamer.rst | 2 +- docs/plugins/getplants.rst | 2 +- docs/plugins/hotkeys.rst | 2 +- docs/plugins/infiniteSky.rst | 2 +- docs/plugins/isoworldremote.rst | 2 +- docs/plugins/jobutils.rst | 2 +- docs/plugins/labormanager.rst | 2 +- docs/plugins/lair.rst | 2 +- docs/plugins/liquids.rst | 2 +- docs/plugins/luasocket.rst | 1 + docs/plugins/manipulator.rst | 2 +- docs/plugins/map-render.rst | 1 + docs/plugins/misery.rst | 2 +- docs/plugins/mode.rst | 2 +- docs/plugins/mousequery.rst | 2 +- docs/plugins/nestboxes.rst | 2 +- docs/plugins/orders.rst | 2 +- docs/plugins/pathable.rst | 1 + docs/plugins/petcapRemover.rst | 2 +- docs/plugins/plants.rst | 2 +- docs/plugins/power-meter.rst | 2 +- docs/plugins/probe.rst | 2 +- docs/plugins/prospector.rst | 2 +- docs/plugins/regrass.rst | 2 +- docs/plugins/rename.rst | 2 +- docs/plugins/rendermax.rst | 2 +- docs/plugins/resume.rst | 2 +- docs/plugins/reveal.rst | 2 +- docs/plugins/ruby.rst | 2 +- docs/plugins/search.rst | 2 +- docs/plugins/seedwatch.rst | 2 +- docs/plugins/showmood.rst | 2 +- docs/plugins/siege-engine.rst | 2 +- docs/plugins/sort.rst | 2 +- docs/plugins/spectate.rst | 2 +- docs/plugins/steam-engine.rst | 2 +- docs/plugins/stockflow.rst | 2 +- docs/plugins/stockpiles.rst | 2 +- docs/plugins/stocks.rst | 2 +- docs/plugins/stonesense.rst | 2 +- docs/plugins/strangemood.rst | 2 +- docs/plugins/tailor.rst | 2 +- docs/plugins/tiletypes.rst | 2 +- docs/plugins/title-folder.rst | 2 +- docs/plugins/title-version.rst | 2 +- docs/plugins/trackstop.rst | 2 +- docs/plugins/tubefill.rst | 2 +- docs/plugins/tweak.rst | 2 +- docs/plugins/workNow.rst | 2 +- docs/plugins/workflow.rst | 2 +- docs/plugins/xlsxreader.rst | 1 + docs/plugins/zone.rst | 2 +- 121 files changed, 138 insertions(+), 124 deletions(-) diff --git a/docs/Tags.rst b/docs/Tags.rst index 294358bd8..d0c785c3c 100644 --- a/docs/Tags.rst +++ b/docs/Tags.rst @@ -1,20 +1,27 @@ :orphan: -- `tag/adventure`: Tools that are useful while in adventure mode. +- `tag/adventure`: Tools that are useful while in adventure mode. Note that some tools only tagged with "fort" might also work in adventure mode, but not always in expected ways. Feel free to experiment, though! - `tag/fort`: Tools that are useful while in fort mode. - `tag/legends`: Tools that are useful while in legends mode. -- `tag/items`: Tools that create or modify in-game items. -- `tag/units`: Tools that create or modify units. -- `tag/jobs`: Tools that create or modify jobs. -- `tag/labors`: Tools that deal with labor assignment. -- `tag/auto`: Tools that automatically manage some aspect of your fortress. -- `tag/map`: Map modification. +- `tag/embark`: Tools that are useful while on the embark screen. - `tag/system`: Tools related to working with DFHack commands or the core DFHack library. +- `tag/dev`: Tools useful for develpers and modders. +- `tag/auto`: Tools that you turn on and then they automatically manage some aspect of your fortress. - `tag/productivity`: Tools that help you do things that you could do manually, but using the tool is better and faster. -- `tag/animals`: Tools that help you manage animals. -- `tag/fix`: Tools that fix specific bugs. - `tag/inspection`: Tools that let you inspect game data. -- `tag/buildings`: Tools that help you work wtih placing or configuring buildings and furniture. +- `tag/design`: Tools that help you design your fort. - `tag/quickfort`: Tools that are involved in creating and playing back blueprints. -- `tag/dev`: Tools useful for develpers and modders. - `tag/interface`: Tools that modify or extend the user interface. +- `tag/fps`: Tools that help you manage FPS drop. +- `tag/fix`: Tools that fix specific bugs. +- `tag/mod`: Tools that introduce new gameplay elements. +- `tag/armok`: Tools that give you complete control over various aspects of the game. +- `tag/animals`: Tools that help you manage animals. +- `tag/buildings`: Tools that help you work with placing or configuring buildings and furniture. +- `tag/items`: Tools that create or modify in-game items. +- `tag/jobs`: Tools that create or modify jobs. +- `tag/map`: Map modification. +- `tag/labors`: Tools that deal with labor assignment. +- `tag/units`: Tools that create or modify units. +- `tag/stockpiles`: Tools that interact wtih stockpiles. +- `tag/trees`: Tools that interact with trees and shrubs. diff --git a/docs/builtins/alias.rst b/docs/builtins/alias.rst index 96f088798..6efaf0776 100644 --- a/docs/builtins/alias.rst +++ b/docs/builtins/alias.rst @@ -1,6 +1,6 @@ alias ===== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`alias` :index:`Configure helper aliases for other DFHack commands. diff --git a/docs/builtins/cls.rst b/docs/builtins/cls.rst index a0478c8df..d6a245081 100644 --- a/docs/builtins/cls.rst +++ b/docs/builtins/cls.rst @@ -1,6 +1,6 @@ cls === -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`cls` :index:`Clear the terminal screen. ` Can also diff --git a/docs/builtins/die.rst b/docs/builtins/die.rst index 54a134433..db487da0e 100644 --- a/docs/builtins/die.rst +++ b/docs/builtins/die.rst @@ -1,6 +1,6 @@ die === -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`die` :index:`Instantly exit DF without saving. diff --git a/docs/builtins/disable.rst b/docs/builtins/disable.rst index 567e9dbb1..758b6ff8f 100644 --- a/docs/builtins/disable.rst +++ b/docs/builtins/disable.rst @@ -1,6 +1,6 @@ disable ======= -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`disable` :index:`Deactivate a DFHack tool that has some persistent effect. diff --git a/docs/builtins/enable.rst b/docs/builtins/enable.rst index b99dd194a..8705bc4ef 100644 --- a/docs/builtins/enable.rst +++ b/docs/builtins/enable.rst @@ -1,6 +1,6 @@ enable ====== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`enable` :index:`Activate a DFHack tool that has some persistent effect. diff --git a/docs/builtins/fpause.rst b/docs/builtins/fpause.rst index c43cedfab..beb783e82 100644 --- a/docs/builtins/fpause.rst +++ b/docs/builtins/fpause.rst @@ -1,6 +1,6 @@ fpause ====== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`fpause` :index:`Forces DF to pause. ` This is useful when diff --git a/docs/builtins/help.rst b/docs/builtins/help.rst index 957a84466..4195f2eaa 100644 --- a/docs/builtins/help.rst +++ b/docs/builtins/help.rst @@ -1,6 +1,6 @@ help ==== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`help` :index:`Display help about a command or plugin. diff --git a/docs/builtins/hide.rst b/docs/builtins/hide.rst index de144b637..087d87726 100644 --- a/docs/builtins/hide.rst +++ b/docs/builtins/hide.rst @@ -1,6 +1,6 @@ hide ==== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`hide` :index:`Hide the DFHack terminal window. diff --git a/docs/builtins/keybinding.rst b/docs/builtins/keybinding.rst index ad759b14c..fd857a42b 100644 --- a/docs/builtins/keybinding.rst +++ b/docs/builtins/keybinding.rst @@ -1,6 +1,6 @@ keybinding ========== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`keybinding` :index:`Create hotkeys that will run DFHack commands. diff --git a/docs/builtins/kill-lua.rst b/docs/builtins/kill-lua.rst index 222795db3..129268fa8 100644 --- a/docs/builtins/kill-lua.rst +++ b/docs/builtins/kill-lua.rst @@ -1,6 +1,6 @@ kill-lua ======== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`kill-lua` :index:`Gracefully stops any currently-running Lua scripts. diff --git a/docs/builtins/load.rst b/docs/builtins/load.rst index b7e3db8f3..cd3aed2c0 100644 --- a/docs/builtins/load.rst +++ b/docs/builtins/load.rst @@ -1,6 +1,6 @@ load ==== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`load` :index:`Load and register a plugin library. diff --git a/docs/builtins/ls.rst b/docs/builtins/ls.rst index 3ae3fb3e1..f03e3e533 100644 --- a/docs/builtins/ls.rst +++ b/docs/builtins/ls.rst @@ -1,6 +1,6 @@ ls == -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`ls` :index:`List available DFHack commands. ` diff --git a/docs/builtins/plug.rst b/docs/builtins/plug.rst index 963660bbb..76e31c432 100644 --- a/docs/builtins/plug.rst +++ b/docs/builtins/plug.rst @@ -1,6 +1,6 @@ plug ==== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`plug` :index:`List available plugins and whether they are enabled. diff --git a/docs/builtins/reload.rst b/docs/builtins/reload.rst index 4417b5185..8449c713d 100644 --- a/docs/builtins/reload.rst +++ b/docs/builtins/reload.rst @@ -1,6 +1,6 @@ reload ====== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`reload` :index:`Reload a loaded plugin. ` Developers diff --git a/docs/builtins/sc-script.rst b/docs/builtins/sc-script.rst index e19af0bf9..17c0efe25 100644 --- a/docs/builtins/sc-script.rst +++ b/docs/builtins/sc-script.rst @@ -1,6 +1,6 @@ sc-script ========= -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`sc-script` :index:`Run commands when game state changes occur. diff --git a/docs/builtins/script.rst b/docs/builtins/script.rst index f15e86bec..e5576ed50 100644 --- a/docs/builtins/script.rst +++ b/docs/builtins/script.rst @@ -1,6 +1,6 @@ script ====== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`script` :index:`Execute a batch file of DFHack commands. diff --git a/docs/builtins/show.rst b/docs/builtins/show.rst index a173231d7..fde642726 100644 --- a/docs/builtins/show.rst +++ b/docs/builtins/show.rst @@ -1,6 +1,6 @@ show ==== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`show` :index:`Unhides the DFHack terminal window. diff --git a/docs/builtins/tags.rst b/docs/builtins/tags.rst index 33ac36fe2..024677cff 100644 --- a/docs/builtins/tags.rst +++ b/docs/builtins/tags.rst @@ -1,6 +1,6 @@ tags ==== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`tags` :index:`List the strings that DFHack tools can be tagged with. diff --git a/docs/builtins/type.rst b/docs/builtins/type.rst index 7c446bc4e..d7cf9588e 100644 --- a/docs/builtins/type.rst +++ b/docs/builtins/type.rst @@ -1,6 +1,6 @@ type ==== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`type` :index:`Describe how a command is implemented. diff --git a/docs/builtins/unload.rst b/docs/builtins/unload.rst index ec385489f..e79d624ec 100644 --- a/docs/builtins/unload.rst +++ b/docs/builtins/unload.rst @@ -1,6 +1,6 @@ unload ====== -Tags: `tag/system` +**Tags:** `tag/system` :dfhack-keybind:`unload` :index:`Unload a plugin from memory. ` diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 9fe83a4b5..680d44c6e 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -1,6 +1,6 @@ 3dveins ======= -Tags: +**Tags:** `tag/fort`, `tag/mod`, `tag/map` :dfhack-keybind:`3dveins` :index:`Rewrite layer veins to expand in 3D space. diff --git a/docs/plugins/RemoteFortressReader.rst b/docs/plugins/RemoteFortressReader.rst index f3bf505ac..da8c3570e 100644 --- a/docs/plugins/RemoteFortressReader.rst +++ b/docs/plugins/RemoteFortressReader.rst @@ -1,6 +1,6 @@ RemoteFortressReader ==================== -Tags: +**Tags:** `tag/dev` :dfhack-keybind:`RemoteFortressReader_version` :dfhack-keybind:`load-art-image-chunk` diff --git a/docs/plugins/add-spatter.rst b/docs/plugins/add-spatter.rst index 6e5a8ddb7..ed3675132 100644 --- a/docs/plugins/add-spatter.rst +++ b/docs/plugins/add-spatter.rst @@ -1,6 +1,6 @@ add-spatter =========== -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/mod`, `tag/items` :index:`Make tagged reactions produce contaminants. ` Give some use to all diff --git a/docs/plugins/autobutcher.rst b/docs/plugins/autobutcher.rst index a6b69338c..7a960012e 100644 --- a/docs/plugins/autobutcher.rst +++ b/docs/plugins/autobutcher.rst @@ -1,6 +1,6 @@ autobutcher =========== -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/fps`, `tag/animals` :dfhack-keybind:`autobutcher` Automatically butcher excess livestock. This plugin monitors how many pets you diff --git a/docs/plugins/autochop.rst b/docs/plugins/autochop.rst index 6893644db..6b0b7125e 100644 --- a/docs/plugins/autochop.rst +++ b/docs/plugins/autochop.rst @@ -1,6 +1,6 @@ autochop ======== -Tags: +**Tags:** `tag/fort`, `tag/auto` :index:`Auto-harvest trees when low on stockpiled logs. ` This plugin can diff --git a/docs/plugins/autoclothing.rst b/docs/plugins/autoclothing.rst index 2e0d03ec1..3feb22520 100644 --- a/docs/plugins/autoclothing.rst +++ b/docs/plugins/autoclothing.rst @@ -1,6 +1,6 @@ autoclothing ============ -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/jobs` :dfhack-keybind:`autoclothing` :index:`Automatically manage clothing work orders. diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst index b42ed9d85..53ed7f1ba 100644 --- a/docs/plugins/autodump.rst +++ b/docs/plugins/autodump.rst @@ -1,6 +1,6 @@ autodump ======== -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/fps`, `tag/items`, `tag/stockpiles` :dfhack-keybind:`autodump` :dfhack-keybind:`autodump-destroy-here` :dfhack-keybind:`autodump-destroy-item` diff --git a/docs/plugins/autofarm.rst b/docs/plugins/autofarm.rst index 9a2169d65..bfe8ad717 100644 --- a/docs/plugins/autofarm.rst +++ b/docs/plugins/autofarm.rst @@ -1,6 +1,6 @@ autofarm ======== -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/buildings` :dfhack-keybind:`autofarm` :index:`Automatically manage farm crop selection. diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index ee1bc8be6..bcff93ba6 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -1,6 +1,6 @@ autogems ======== -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/jobs` :dfhack-keybind:`autogems-reload` :index:`Automatically cut rough gems. ` diff --git a/docs/plugins/autohauler.rst b/docs/plugins/autohauler.rst index 82561fa2a..e0e1b2bac 100644 --- a/docs/plugins/autohauler.rst +++ b/docs/plugins/autohauler.rst @@ -1,6 +1,6 @@ autohauler ========== -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/labors` :dfhack-keybind:`autohauler` :index:`Automatically manage hauling labors. diff --git a/docs/plugins/autolabor.rst b/docs/plugins/autolabor.rst index dc14a40bb..1d02fcfe8 100644 --- a/docs/plugins/autolabor.rst +++ b/docs/plugins/autolabor.rst @@ -1,6 +1,6 @@ autolabor ========= -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/labors` :dfhack-keybind:`autolabor` :index:`Automatically manage dwarf labors. diff --git a/docs/plugins/automaterial.rst b/docs/plugins/automaterial.rst index 3b9a68b7a..1c237f260 100644 --- a/docs/plugins/automaterial.rst +++ b/docs/plugins/automaterial.rst @@ -1,6 +1,6 @@ automaterial ============ -Tags: +**Tags:** `tag/fort`, `tag/productivity`, `tag/design`, `tag/buildings`, `tag/map` :index:`Sorts building materials by recent usage. ` This makes building diff --git a/docs/plugins/automelt.rst b/docs/plugins/automelt.rst index 8fe995d23..190bffc06 100644 --- a/docs/plugins/automelt.rst +++ b/docs/plugins/automelt.rst @@ -1,6 +1,6 @@ automelt ======== -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/items`, `tag/stockpiles` :index:`Quickly designate items to be melted. ` When `enabled `, this diff --git a/docs/plugins/autonestbox.rst b/docs/plugins/autonestbox.rst index 6842bef5c..2f874c452 100644 --- a/docs/plugins/autonestbox.rst +++ b/docs/plugins/autonestbox.rst @@ -1,6 +1,6 @@ autonestbox =========== -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/animals` :dfhack-keybind:`autonestbox` Auto-assign egg-laying female pets to nestbox zones. Requires that you create diff --git a/docs/plugins/autotrade.rst b/docs/plugins/autotrade.rst index 68aa2d50d..35657fc88 100644 --- a/docs/plugins/autotrade.rst +++ b/docs/plugins/autotrade.rst @@ -1,6 +1,6 @@ autotrade ========= -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/items`, `tag/stockpiles` :index:`Quickly designate items to be traded. ` When `enabled `, diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst index 6a0adfe58..48ffdd883 100644 --- a/docs/plugins/blueprint.rst +++ b/docs/plugins/blueprint.rst @@ -1,6 +1,6 @@ blueprint ========= -Tags: +**Tags:** `tag/fort`, `tag/design`, `tag/quickfort`, `tag/map` :dfhack-keybind:`blueprint` :index:`Record a live game map in a quickfort blueprint. diff --git a/docs/plugins/building-hacks.rst b/docs/plugins/building-hacks.rst index 106173c47..1f8ee1b2b 100644 --- a/docs/plugins/building-hacks.rst +++ b/docs/plugins/building-hacks.rst @@ -1,5 +1,6 @@ building-hacks ============== +**Tags:** `tag/fort`, `tag/mod`, `tag/buildings` Provides a Lua API for creating powered workshops. diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index 6aba86209..be95dc6f1 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -1,6 +1,6 @@ buildingplan ============ -Tags: +**Tags:** `tag/fort`, `tag/design`, `tag/quickfort`, `tag/buildings`, `tag/map` :dfhack-keybind:`buildingplan` :index:`Plan building construction before you have materials. diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst index 29b81330d..4728e2cd5 100644 --- a/docs/plugins/burrows.rst +++ b/docs/plugins/burrows.rst @@ -1,6 +1,6 @@ burrows ======= -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/productivity`, `tag/units` :dfhack-keybind:`burrow` :index:`Auto-expand burrows as you dig. diff --git a/docs/plugins/changeitem.rst b/docs/plugins/changeitem.rst index 93a10cbfa..68c53f821 100644 --- a/docs/plugins/changeitem.rst +++ b/docs/plugins/changeitem.rst @@ -1,6 +1,6 @@ changeitem ========== -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/armok`, `tag/items` :dfhack-keybind:`changeitem` :index:`Change item material and base quality. diff --git a/docs/plugins/changelayer.rst b/docs/plugins/changelayer.rst index 5298cf955..1267fbdac 100644 --- a/docs/plugins/changelayer.rst +++ b/docs/plugins/changelayer.rst @@ -1,6 +1,6 @@ changelayer =========== -Tags: +**Tags:** `tag/fort`, `tag/armok`, `tag/map` :dfhack-keybind:`changelayer` :index:`Change the material of an entire geology layer. diff --git a/docs/plugins/changevein.rst b/docs/plugins/changevein.rst index e23c3f063..96c68de76 100644 --- a/docs/plugins/changevein.rst +++ b/docs/plugins/changevein.rst @@ -1,6 +1,6 @@ changevein ========== -Tags: +**Tags:** `tag/fort`, `tag/armok`, `tag/map` :dfhack-keybind:`changevein` :index:`Change the material of a mineral inclusion. diff --git a/docs/plugins/cleanconst.rst b/docs/plugins/cleanconst.rst index 37416c551..c47a8c6dd 100644 --- a/docs/plugins/cleanconst.rst +++ b/docs/plugins/cleanconst.rst @@ -1,6 +1,6 @@ cleanconst ========== -Tags: +**Tags:** `tag/fort`, `tag/fps`, `tag/map` :dfhack-keybind:`cleanconst` :index:`Cleans up construction materials. diff --git a/docs/plugins/cleaners.rst b/docs/plugins/cleaners.rst index 4a6aee1e3..20fa792db 100644 --- a/docs/plugins/cleaners.rst +++ b/docs/plugins/cleaners.rst @@ -3,7 +3,7 @@ cleaners ======== -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/fps`, `tag/items`, `tag/map`, `tag/units` :dfhack-keybind:`clean` :dfhack-keybind:`spotclean` diff --git a/docs/plugins/cleanowned.rst b/docs/plugins/cleanowned.rst index c90fe0569..409c39345 100644 --- a/docs/plugins/cleanowned.rst +++ b/docs/plugins/cleanowned.rst @@ -1,6 +1,6 @@ cleanowned ========== -Tags: +**Tags:** `tag/fort`, `tag/productivity`, `tag/items` :dfhack-keybind:`cleanowned` :index:`Confiscates and dumps garbage owned by dwarves. diff --git a/docs/plugins/command-prompt.rst b/docs/plugins/command-prompt.rst index 8e6c9e885..312734b97 100644 --- a/docs/plugins/command-prompt.rst +++ b/docs/plugins/command-prompt.rst @@ -1,6 +1,6 @@ command-prompt ============== -Tags: +**Tags:** `tag/system` :dfhack-keybind:`command-prompt` :index:`An in-game DFHack terminal where you can enter other commands. diff --git a/docs/plugins/confirm.rst b/docs/plugins/confirm.rst index aceb1e6d6..de2556b6f 100644 --- a/docs/plugins/confirm.rst +++ b/docs/plugins/confirm.rst @@ -1,6 +1,6 @@ confirm ======= -Tags: +**Tags:** `tag/fort`, `tag/interface` :dfhack-keybind:`confirm` :index:`Adds confirmation dialogs for destructive actions. diff --git a/docs/plugins/createitem.rst b/docs/plugins/createitem.rst index b49a85be4..38f072e62 100644 --- a/docs/plugins/createitem.rst +++ b/docs/plugins/createitem.rst @@ -1,6 +1,6 @@ createitem ========== -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/armok`, `tag/items` :dfhack-keybind:`createitem` :index:`Create arbitrary items. ` You can diff --git a/docs/plugins/cursecheck.rst b/docs/plugins/cursecheck.rst index 17cdacf26..ecb4dbd2f 100644 --- a/docs/plugins/cursecheck.rst +++ b/docs/plugins/cursecheck.rst @@ -1,6 +1,6 @@ cursecheck ========== -Tags: +**Tags:** `tag/system`, `tag/interface` :dfhack-keybind:`cursecheck` :index:`Check for cursed creatures. ` diff --git a/docs/plugins/cxxrandom.rst b/docs/plugins/cxxrandom.rst index f3ed8f86d..0016fb8d0 100644 --- a/docs/plugins/cxxrandom.rst +++ b/docs/plugins/cxxrandom.rst @@ -1,5 +1,6 @@ cxxrandom ========= +**Tags:** `tag/dev` Provides a Lua API for random distributions. diff --git a/docs/plugins/debug.rst b/docs/plugins/debug.rst index 2b3b20359..4cc94b217 100644 --- a/docs/plugins/debug.rst +++ b/docs/plugins/debug.rst @@ -1,6 +1,6 @@ debug ===== -Tags: +**Tags:** `tag/dev` :dfhack-keybind:`debugfilter` :index:`Configure verbosity of DFHack debug output. diff --git a/docs/plugins/deramp.rst b/docs/plugins/deramp.rst index 7f4a8ce24..6126d2828 100644 --- a/docs/plugins/deramp.rst +++ b/docs/plugins/deramp.rst @@ -1,6 +1,6 @@ deramp ====== -Tags: +**Tags:** `tag/fort`, `tag/armok`, `tag/map` :dfhack-keybind:`deramp` :index:`Removes all ramps designated for removal from the map. diff --git a/docs/plugins/dig-now.rst b/docs/plugins/dig-now.rst index cdb21c01a..7d3beedc0 100644 --- a/docs/plugins/dig-now.rst +++ b/docs/plugins/dig-now.rst @@ -1,6 +1,6 @@ dig-now ======= -Tags: +**Tags:** `tag/fort`, `tag/armok`, `tag/map` :dfhack-keybind:`dig-now` :index:`Instantly complete dig designations. diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst index 8fbf1a910..f4e0c8acb 100644 --- a/docs/plugins/dig.rst +++ b/docs/plugins/dig.rst @@ -3,7 +3,7 @@ dig === -Tags: +**Tags:** `tag/fort`, `tag/productivity`, `tag/design`, `tag/map` :dfhack-keybind:`digv` :dfhack-keybind:`digvx` :dfhack-keybind:`digl` diff --git a/docs/plugins/digFlood.rst b/docs/plugins/digFlood.rst index cb8018145..1a17c32a7 100644 --- a/docs/plugins/digFlood.rst +++ b/docs/plugins/digFlood.rst @@ -1,6 +1,6 @@ digFlood ======== -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/map` :dfhack-keybind:`digFlood` :index:`Digs out veins as they are discovered. diff --git a/docs/plugins/diggingInvaders.rst b/docs/plugins/diggingInvaders.rst index d2fada0d4..4891bf8d4 100644 --- a/docs/plugins/diggingInvaders.rst +++ b/docs/plugins/diggingInvaders.rst @@ -1,6 +1,6 @@ diggingInvaders =============== -Tags: +**Tags:** `tag/fort`, `tag/mod`, `tag/map` :dfhack-keybind:`diggingInvaders` :index:`Invaders dig and destroy to get to your dwarves. diff --git a/docs/plugins/dwarfmonitor.rst b/docs/plugins/dwarfmonitor.rst index 75b71d06c..a7f42bb81 100644 --- a/docs/plugins/dwarfmonitor.rst +++ b/docs/plugins/dwarfmonitor.rst @@ -1,6 +1,6 @@ dwarfmonitor ============ -Tags: +**Tags:** `tag/fort`, `tag/inspection`, `tag/units` :dfhack-keybind:`dwarfmonitor` :index:`Measure fort happiness and efficiency. diff --git a/docs/plugins/dwarfvet.rst b/docs/plugins/dwarfvet.rst index 6a1c5a60f..2db4a9595 100644 --- a/docs/plugins/dwarfvet.rst +++ b/docs/plugins/dwarfvet.rst @@ -1,6 +1,6 @@ dwarfvet ======== -Tags: +**Tags:** `tag/fort`, `tag/mod`, `tag/animals` :dfhack-keybind:`dwarfvet` :index:`Allows animals to be treated at animal hospitals. diff --git a/docs/plugins/embark-assistant.rst b/docs/plugins/embark-assistant.rst index 3a6081655..b9a7e4ee2 100644 --- a/docs/plugins/embark-assistant.rst +++ b/docs/plugins/embark-assistant.rst @@ -1,6 +1,6 @@ embark-assistant ================ -Tags: +**Tags:** `tag/fort`, `tag/embark`, `tag/interface` :dfhack-keybind:`embark-assistant` :index:`Embark site selection support. diff --git a/docs/plugins/embark-tools.rst b/docs/plugins/embark-tools.rst index 987b76df6..52532e4f2 100644 --- a/docs/plugins/embark-tools.rst +++ b/docs/plugins/embark-tools.rst @@ -1,6 +1,6 @@ embark-tools ============ -Tags: +**Tags:** `tag/fort`, `tag/embark`, `tag/interface` :dfhack-keybind:`embark-tools` :index:`Extend the embark screen functionality. diff --git a/docs/plugins/eventful.rst b/docs/plugins/eventful.rst index 892f233bb..102287b77 100644 --- a/docs/plugins/eventful.rst +++ b/docs/plugins/eventful.rst @@ -1,5 +1,6 @@ eventful ======== +**Tags:** `tag/dev`, `tag/mod` Provides a Lua API for reacting to in-game events. diff --git a/docs/plugins/fastdwarf.rst b/docs/plugins/fastdwarf.rst index e06f2564c..2e09b8a4d 100644 --- a/docs/plugins/fastdwarf.rst +++ b/docs/plugins/fastdwarf.rst @@ -1,6 +1,6 @@ fastdwarf ========= -Tags: +**Tags:** `tag/fort`, `tag/armok`, `tag/units` :dfhack-keybind:`fastdwarf` Dwarves teleport and/or finish jobs instantly. diff --git a/docs/plugins/filltraffic.rst b/docs/plugins/filltraffic.rst index 5056ec830..5a3beaab7 100644 --- a/docs/plugins/filltraffic.rst +++ b/docs/plugins/filltraffic.rst @@ -3,7 +3,7 @@ filltraffic =========== -Tags: +**Tags:** `tag/fort`, `tag/productivity`, `tag/design`, `tag/map` :dfhack-keybind:`` Usage: diff --git a/docs/plugins/fix-unit-occupancy.rst b/docs/plugins/fix-unit-occupancy.rst index 48efd7255..28479791f 100644 --- a/docs/plugins/fix-unit-occupancy.rst +++ b/docs/plugins/fix-unit-occupancy.rst @@ -1,6 +1,6 @@ fix-unit-occupancy ================== -Tags: +**Tags:** `tag/fort`, `tag/fix`, `tag/map` :dfhack-keybind:`` Fix phantom unit occupancy issues. For example, if you see "unit blocking tile" diff --git a/docs/plugins/fixveins.rst b/docs/plugins/fixveins.rst index 940c13b7a..873e1770a 100644 --- a/docs/plugins/fixveins.rst +++ b/docs/plugins/fixveins.rst @@ -1,6 +1,6 @@ fixveins ======== -Tags: +**Tags:** `tag/fort`, `tag/fix`, `tag/map` :dfhack-keybind:`fixveins` Restore missing mineral inclusions. This tool can also remove invalid references diff --git a/docs/plugins/flows.rst b/docs/plugins/flows.rst index 354b0078e..a347f00e0 100644 --- a/docs/plugins/flows.rst +++ b/docs/plugins/flows.rst @@ -1,6 +1,6 @@ flows ===== -Tags: +**Tags:** `tag/fort`, `tag/inspection`, `tag/map` :dfhack-keybind:`flows` Counts map blocks with flowing liquids.. If you suspect that your magma sea diff --git a/docs/plugins/follow.rst b/docs/plugins/follow.rst index a0c15547e..5f43e2fdd 100644 --- a/docs/plugins/follow.rst +++ b/docs/plugins/follow.rst @@ -1,6 +1,6 @@ follow ====== -Tags: +**Tags:** `tag/fort`, `tag/interface`, `tag/units` :dfhack-keybind:`follow` Make the screen follow the selected unit. Once you exit from the current menu or diff --git a/docs/plugins/forceequip.rst b/docs/plugins/forceequip.rst index 217085fe4..51d48390c 100644 --- a/docs/plugins/forceequip.rst +++ b/docs/plugins/forceequip.rst @@ -1,6 +1,6 @@ forceequip ========== -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/items`, `tag/units` :dfhack-keybind:`forceequip` Move items into a unit's inventory. This tool is typically used to equip diff --git a/docs/plugins/generated-creature-renamer.rst b/docs/plugins/generated-creature-renamer.rst index 1615ba32d..39983940b 100644 --- a/docs/plugins/generated-creature-renamer.rst +++ b/docs/plugins/generated-creature-renamer.rst @@ -1,6 +1,6 @@ generated-creature-renamer ========================== -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/legends`, `tag/units` :dfhack-keybind:`list-generated` :dfhack-keybind:`save-generated-raws` diff --git a/docs/plugins/getplants.rst b/docs/plugins/getplants.rst index 167ac72f2..21325e8f2 100644 --- a/docs/plugins/getplants.rst +++ b/docs/plugins/getplants.rst @@ -1,6 +1,6 @@ getplants ========= -Tags: +**Tags:** `tag/fort`, `tag/productivity` :dfhack-keybind:`getplants` Designate trees for chopping and shrubs for gathering. Specify the types of diff --git a/docs/plugins/hotkeys.rst b/docs/plugins/hotkeys.rst index 49064db0a..fbf4a9a76 100644 --- a/docs/plugins/hotkeys.rst +++ b/docs/plugins/hotkeys.rst @@ -1,6 +1,6 @@ hotkeys ======= -Tags: +**Tags:** `tag/system`, `tag/productivity`, `tag/interface` :dfhack-keybind:`hotkeys` Show all dfhack keybindings in current context. The command opens an in-game diff --git a/docs/plugins/infiniteSky.rst b/docs/plugins/infiniteSky.rst index 96f141de5..ceb08aa0e 100644 --- a/docs/plugins/infiniteSky.rst +++ b/docs/plugins/infiniteSky.rst @@ -1,6 +1,6 @@ infiniteSky =========== -Tags: +**Tags:** `tag/fort`, `tag/map` :dfhack-keybind:`infiniteSky` Automatically allocates new z-levels of sky at the top of the map as you build diff --git a/docs/plugins/isoworldremote.rst b/docs/plugins/isoworldremote.rst index 2c359fac2..84babe66a 100644 --- a/docs/plugins/isoworldremote.rst +++ b/docs/plugins/isoworldremote.rst @@ -1,5 +1,5 @@ isoworldremote ============== -Tags: +**Tags:** `tag/dev`, `tag/mod` Provides a `remote API ` used by Isoworld. diff --git a/docs/plugins/jobutils.rst b/docs/plugins/jobutils.rst index 2ecf2b591..5dcb0c85f 100644 --- a/docs/plugins/jobutils.rst +++ b/docs/plugins/jobutils.rst @@ -2,7 +2,7 @@ jobutils ======== -Tags: +**Tags:** `tag/fort`, `tag/inspection`, `tag/jobs` :dfhack-keybind:`job` :dfhack-keybind:`job-duplicate` :dfhack-keybind:`job-material` diff --git a/docs/plugins/labormanager.rst b/docs/plugins/labormanager.rst index 103ecde2f..1a25cedae 100644 --- a/docs/plugins/labormanager.rst +++ b/docs/plugins/labormanager.rst @@ -1,6 +1,6 @@ labormanager ============ -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/labors` :dfhack-keybind:`labormanager` Automatically manage dwarf labors. Labormanager is derived from `autolabor` diff --git a/docs/plugins/lair.rst b/docs/plugins/lair.rst index b4a7ed524..43bb06072 100644 --- a/docs/plugins/lair.rst +++ b/docs/plugins/lair.rst @@ -1,6 +1,6 @@ lair ==== -Tags: +**Tags:** `tag/fort`, `tag/armok`, `tag/map` :dfhack-keybind:`lair` Mark the map as a monster lair. This avoids item scatter when the fortress is diff --git a/docs/plugins/liquids.rst b/docs/plugins/liquids.rst index 61e4bcfe2..808089591 100644 --- a/docs/plugins/liquids.rst +++ b/docs/plugins/liquids.rst @@ -2,7 +2,7 @@ liquids ======= -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/armok`, `tag/map` :dfhack-keybind:`liquids` :dfhack-keybind:`liquids-here` diff --git a/docs/plugins/luasocket.rst b/docs/plugins/luasocket.rst index 6f7c96cae..cbbf09f7e 100644 --- a/docs/plugins/luasocket.rst +++ b/docs/plugins/luasocket.rst @@ -1,5 +1,6 @@ luasocket ========= +**Tags:** `tag/dev`, `tag/mod` Provides a Lua API for accessing network sockets. diff --git a/docs/plugins/manipulator.rst b/docs/plugins/manipulator.rst index 87ac4e088..07d1d4ab6 100644 --- a/docs/plugins/manipulator.rst +++ b/docs/plugins/manipulator.rst @@ -1,6 +1,6 @@ manipulator =========== -Tags: +**Tags:** `tag/fort`, `tag/productivity`, `tag/interface`, `tag/labors` An in-game labor management interface. It is equivalent to the popular Dwarf Therapist utility. diff --git a/docs/plugins/map-render.rst b/docs/plugins/map-render.rst index 2bcb32adf..6b7f1a5d9 100644 --- a/docs/plugins/map-render.rst +++ b/docs/plugins/map-render.rst @@ -1,5 +1,6 @@ map-render ========== +**Tags:** `tag/dev` Provides a Lua API for rerendering portions of the map. diff --git a/docs/plugins/misery.rst b/docs/plugins/misery.rst index 9a3a632f2..5d11f98cb 100644 --- a/docs/plugins/misery.rst +++ b/docs/plugins/misery.rst @@ -1,6 +1,6 @@ misery ====== -Tags: +**Tags:** `tag/fort`, `tag/armok`, `tag/units` :dfhack-keybind:`misery` Increase the intensity of negative dwarven thoughts. diff --git a/docs/plugins/mode.rst b/docs/plugins/mode.rst index 225a49e1e..8c33d6ec3 100644 --- a/docs/plugins/mode.rst +++ b/docs/plugins/mode.rst @@ -1,6 +1,6 @@ mode ==== -Tags: +**Tags:** `tag/dev` :dfhack-keybind:`mode` This command lets you see and change the game mode directly. diff --git a/docs/plugins/mousequery.rst b/docs/plugins/mousequery.rst index 39f401bb2..7e01de6d0 100644 --- a/docs/plugins/mousequery.rst +++ b/docs/plugins/mousequery.rst @@ -1,6 +1,6 @@ mousequery ========== -Tags: `tag/fort`, `tag/interface` +**Tags:** `tag/fort`, `tag/productivity`, `tag/interface` :dfhack-keybind:`mousequery` Adds mouse controls to the DF interface. For example, with ``mousequery`` you diff --git a/docs/plugins/nestboxes.rst b/docs/plugins/nestboxes.rst index fb9df20a9..1558a7357 100644 --- a/docs/plugins/nestboxes.rst +++ b/docs/plugins/nestboxes.rst @@ -1,6 +1,6 @@ nestboxes ========= -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/animals` Protect fertile eggs incubating in a nestbox. This plugin will automatically scan for and forbid fertile eggs incubating in a nestbox so that dwarves won't diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index 2506bc582..3be1f82b2 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -1,6 +1,6 @@ orders ====== -Tags: +**Tags:** `tag/fort`, `tag/productivity`, `tag/jobs` :dfhack-keybind:`orders` Manage manager orders. diff --git a/docs/plugins/pathable.rst b/docs/plugins/pathable.rst index 7a906ad89..6368d1c7e 100644 --- a/docs/plugins/pathable.rst +++ b/docs/plugins/pathable.rst @@ -1,5 +1,6 @@ pathable ======== +**Tags:** `tag/dev`, `tag/inspection`, `tag/interface`, `tag/map` Marks tiles that are reachable from the cursor. This plugin provides a Lua API, but no direct commands. diff --git a/docs/plugins/petcapRemover.rst b/docs/plugins/petcapRemover.rst index 3d9739ea3..95a9e60bd 100644 --- a/docs/plugins/petcapRemover.rst +++ b/docs/plugins/petcapRemover.rst @@ -1,6 +1,6 @@ petcapRemover ============= -Tags: +**Tags:** `tag/fort`, `tag/armok`, `tag/animals` :dfhack-keybind:`petcapRemover` Modify the pet population cap. In vanilla DF, pets will not reproduce unless the diff --git a/docs/plugins/plants.rst b/docs/plugins/plants.rst index 4fc87f353..2021bc103 100644 --- a/docs/plugins/plants.rst +++ b/docs/plugins/plants.rst @@ -2,7 +2,7 @@ plants ====== -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/armok`, `tag/map` :dfhack-keybind:`plant` Grow shrubs or trees. diff --git a/docs/plugins/power-meter.rst b/docs/plugins/power-meter.rst index 7186a413d..72a49b72c 100644 --- a/docs/plugins/power-meter.rst +++ b/docs/plugins/power-meter.rst @@ -1,6 +1,6 @@ power-meter =========== -Tags: +**Tags:** `tag/fort`, `tag/mod`, `tag/buildings` Allow presure plates to measure power. If you run `gui/power-meter` while building a pressure plate, the pressure plate can be modified to detect power diff --git a/docs/plugins/probe.rst b/docs/plugins/probe.rst index 614d0bc81..0285bb821 100644 --- a/docs/plugins/probe.rst +++ b/docs/plugins/probe.rst @@ -1,6 +1,6 @@ probe ===== -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/inspection`, `tag/map` :dfhack-keybind:`probe` :dfhack-keybind:`bprobe` :dfhack-keybind:`cprobe` diff --git a/docs/plugins/prospector.rst b/docs/plugins/prospector.rst index 718f92fa6..7ff3b4313 100644 --- a/docs/plugins/prospector.rst +++ b/docs/plugins/prospector.rst @@ -2,7 +2,7 @@ prospector ========== -Tags: +**Tags:** `tag/fort`, `tag/embark`, `tag/inspection`, `tag/map` :dfhack-keybind:`prospect` Shows a summary of resources that exist on the map. It can also calculate an diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst index d47e98d19..98590a73e 100644 --- a/docs/plugins/regrass.rst +++ b/docs/plugins/regrass.rst @@ -1,6 +1,6 @@ regrass ======= -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/armok`, `tag/animals` :dfhack-keybind:`regrass` Regrows all the grass. Use this command if your grazers have eaten everything diff --git a/docs/plugins/rename.rst b/docs/plugins/rename.rst index 0c4bc9ddb..34695e19a 100644 --- a/docs/plugins/rename.rst +++ b/docs/plugins/rename.rst @@ -1,6 +1,6 @@ rename ====== -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/productivity` :dfhack-keybind:`rename` Easily rename things. Use `gui/rename` for an in-game interface. diff --git a/docs/plugins/rendermax.rst b/docs/plugins/rendermax.rst index 641a7a248..83e9ee461 100644 --- a/docs/plugins/rendermax.rst +++ b/docs/plugins/rendermax.rst @@ -1,6 +1,6 @@ rendermax ========= -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/mod` :dfhack-keybind:`rendermax` Modify the map lighting. This plugin provides a collection of OpenGL lighting diff --git a/docs/plugins/resume.rst b/docs/plugins/resume.rst index 7afb9be0d..2f28d7083 100644 --- a/docs/plugins/resume.rst +++ b/docs/plugins/resume.rst @@ -1,6 +1,6 @@ resume ====== -Tags: +**Tags:** `tag/fort`, `tag/productivity` :dfhack-keybind:`resume` Color planned buildings based on their suspend status. When enabled, this plugin diff --git a/docs/plugins/reveal.rst b/docs/plugins/reveal.rst index f8611d033..fb69d9a02 100644 --- a/docs/plugins/reveal.rst +++ b/docs/plugins/reveal.rst @@ -2,7 +2,7 @@ reveal ====== -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/inspection`, `tag/armok`, `tag/map` :dfhack-keybind:`reveal` :dfhack-keybind:`unreveal` :dfhack-keybind:`revforget` diff --git a/docs/plugins/ruby.rst b/docs/plugins/ruby.rst index beb9be4a1..d3b89d3a9 100644 --- a/docs/plugins/ruby.rst +++ b/docs/plugins/ruby.rst @@ -2,7 +2,7 @@ ruby ==== -Tags: +**Tags:** `tag/dev` :dfhack-keybind:`rb` :dfhack-keybind:`rb_eval` diff --git a/docs/plugins/search.rst b/docs/plugins/search.rst index edf6fb47c..41ae1c922 100644 --- a/docs/plugins/search.rst +++ b/docs/plugins/search.rst @@ -2,7 +2,7 @@ search ====== -Tags: +**Tags:** `tag/fort`, `tag/productivity`, `tag/interface` Adds search capabilities to the UI. The Stocks, Animals, Trading, Stockpile, Noble (assignment candidates), Military (position candidates), Burrows (unit diff --git a/docs/plugins/seedwatch.rst b/docs/plugins/seedwatch.rst index 2b04b3fa6..bd51fdd2e 100644 --- a/docs/plugins/seedwatch.rst +++ b/docs/plugins/seedwatch.rst @@ -1,6 +1,6 @@ seedwatch ========= -Tags: +**Tags:** `tag/fort`, `tag/auto` :dfhack-keybind:`seedwatch` Manages seed and plant cooking based on seed stock levels. diff --git a/docs/plugins/showmood.rst b/docs/plugins/showmood.rst index 8afe4c629..42af0e7a8 100644 --- a/docs/plugins/showmood.rst +++ b/docs/plugins/showmood.rst @@ -1,6 +1,6 @@ showmood ======== -Tags: +**Tags:** `tag/fort`, `tag/inspection`, `tag/jobs` :dfhack-keybind:`showmood` Shows all items needed for the active strange mood. diff --git a/docs/plugins/siege-engine.rst b/docs/plugins/siege-engine.rst index a4cfed2ba..9255110bd 100644 --- a/docs/plugins/siege-engine.rst +++ b/docs/plugins/siege-engine.rst @@ -1,6 +1,6 @@ siege-engine ============ -Tags: +**Tags:** `tag/fort`, `tag/mod`, `tag/buildings` Extend the functionality and usability of siege engines. Siege engines in DF haven't been updated since the game was 2D, and can only aim in four diff --git a/docs/plugins/sort.rst b/docs/plugins/sort.rst index 12684175f..f3ba6e6fb 100644 --- a/docs/plugins/sort.rst +++ b/docs/plugins/sort.rst @@ -1,6 +1,6 @@ sort ==== -Tags: +**Tags:** `tag/fort`, `tag/productivity`, `tag/interface` :dfhack-keybind:`sort-items` :dfhack-keybind:`sort-units` diff --git a/docs/plugins/spectate.rst b/docs/plugins/spectate.rst index 983086c94..08cc3f819 100644 --- a/docs/plugins/spectate.rst +++ b/docs/plugins/spectate.rst @@ -1,6 +1,6 @@ spectate ======== -Tags: +**Tags:** `tag/fort`, `tag/units` Automatically follow exciting dwarves. diff --git a/docs/plugins/steam-engine.rst b/docs/plugins/steam-engine.rst index 35cd490f5..0fe245d63 100644 --- a/docs/plugins/steam-engine.rst +++ b/docs/plugins/steam-engine.rst @@ -1,6 +1,6 @@ steam-engine ============ -Tags: +**Tags:** `tag/fort`, `tag/mod`, `tag/buildings` Allow modded steam engine buildings to function. The steam-engine plugin detects custom workshops with STEAM_ENGINE in their token, and turns them into real diff --git a/docs/plugins/stockflow.rst b/docs/plugins/stockflow.rst index 0e2f5a1ad..48647b869 100644 --- a/docs/plugins/stockflow.rst +++ b/docs/plugins/stockflow.rst @@ -1,6 +1,6 @@ stockflow ========= -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/jobs`, `tag/stockpiles` :dfhack-keybind:`stockflow` Queue manager jobs based on free space in stockpiles. With this plugin, the diff --git a/docs/plugins/stockpiles.rst b/docs/plugins/stockpiles.rst index 76d52117d..0c1000456 100644 --- a/docs/plugins/stockpiles.rst +++ b/docs/plugins/stockpiles.rst @@ -2,7 +2,7 @@ stockpiles ========== -Tags: +**Tags:** `tag/fort`, `tag/productivity`, `tag/design`, `tag/stockpiles` :dfhack-keybind:`copystock` :dfhack-keybind:`savestock` :dfhack-keybind:`loadstock` diff --git a/docs/plugins/stocks.rst b/docs/plugins/stocks.rst index 076f2ab6f..5506b1d28 100644 --- a/docs/plugins/stocks.rst +++ b/docs/plugins/stocks.rst @@ -1,6 +1,6 @@ stocks ====== -Tags: +**Tags:** `tag/fort`, `tag/productivity`, `tag/interface` :dfhack-keybind:`stocks` Enhanced fortress stock management interface. When the plugin is enabled, two diff --git a/docs/plugins/stonesense.rst b/docs/plugins/stonesense.rst index 59212444b..3bb271477 100644 --- a/docs/plugins/stonesense.rst +++ b/docs/plugins/stonesense.rst @@ -1,6 +1,6 @@ stonesense ========== -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/interface`, `tag/map` :dfhack-keybind:`stonesense` :dfhack-keybind:`ssense` diff --git a/docs/plugins/strangemood.rst b/docs/plugins/strangemood.rst index d6065f8eb..0198af136 100644 --- a/docs/plugins/strangemood.rst +++ b/docs/plugins/strangemood.rst @@ -1,6 +1,6 @@ strangemood =========== -Tags: +**Tags:** `tag/fort`, `tag/armok`, `tag/units` :dfhack-keybind:`strangemood` Triggers a strange mood. diff --git a/docs/plugins/tailor.rst b/docs/plugins/tailor.rst index 19574bc3e..93072fb1b 100644 --- a/docs/plugins/tailor.rst +++ b/docs/plugins/tailor.rst @@ -1,6 +1,6 @@ tailor ====== -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/jobs` :dfhack-keybind:`tailor` Automatically keep your dwarves in fresh clothing. Whenever the bookkeeper diff --git a/docs/plugins/tiletypes.rst b/docs/plugins/tiletypes.rst index cbdbd6cc5..185f283db 100644 --- a/docs/plugins/tiletypes.rst +++ b/docs/plugins/tiletypes.rst @@ -3,7 +3,7 @@ tiletypes ========= -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/armok`, `tag/map` :dfhack-keybind:`tiletypes` :dfhack-keybind:`tiletypes-command` :dfhack-keybind:`tiletypes-here` diff --git a/docs/plugins/title-folder.rst b/docs/plugins/title-folder.rst index 3156a9e1c..beddfc9cb 100644 --- a/docs/plugins/title-folder.rst +++ b/docs/plugins/title-folder.rst @@ -1,6 +1,6 @@ title-folder ============= -Tags: +**Tags:** `tag/system`, `tag/interface` Displays the DF folder name in the window title bar. diff --git a/docs/plugins/title-version.rst b/docs/plugins/title-version.rst index 267179f14..d9d35a85f 100644 --- a/docs/plugins/title-version.rst +++ b/docs/plugins/title-version.rst @@ -1,6 +1,6 @@ title-version ============= -Tags: +**Tags:** `tag/system`, `tag/interface` Displays the DFHack version on DF's title screen. diff --git a/docs/plugins/trackstop.rst b/docs/plugins/trackstop.rst index 72466a1bf..cf5a9853f 100644 --- a/docs/plugins/trackstop.rst +++ b/docs/plugins/trackstop.rst @@ -1,6 +1,6 @@ trackstop ========= -Tags: +**Tags:** `tag/fort`, `tag/interface`, `tag/mod`, `tag/buildings` Adds dynamic configuration options for track stops. When enabled, this plugin adds a :kbd:`q` menu for track stops, which is completely blank in vanilla DF. diff --git a/docs/plugins/tubefill.rst b/docs/plugins/tubefill.rst index 15a9ba2f2..404a180b2 100644 --- a/docs/plugins/tubefill.rst +++ b/docs/plugins/tubefill.rst @@ -1,6 +1,6 @@ tubefill ======== -Tags: +**Tags:** `tag/fort`, `tag/armok`, `tag/map` :dfhack-keybind:`tubefill` Replentishes mined-out adamantine. Veins that were hollow will be left alone. diff --git a/docs/plugins/tweak.rst b/docs/plugins/tweak.rst index fff1a4512..03489845e 100644 --- a/docs/plugins/tweak.rst +++ b/docs/plugins/tweak.rst @@ -1,6 +1,6 @@ tweak ===== -Tags: +**Tags:** `tag/adventure`, `tag/fort`, `tag/interface`, `tag/fps`, `tag/fix`, `tag/armok` :dfhack-keybind:`tweak` Contains various tweaks for minor bugs. diff --git a/docs/plugins/workNow.rst b/docs/plugins/workNow.rst index dda3fea5c..895556ce3 100644 --- a/docs/plugins/workNow.rst +++ b/docs/plugins/workNow.rst @@ -1,6 +1,6 @@ workNow ======= -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/jobs` :dfhack-keybind:`workNow` Reduce the time that dwarves idle after completing a job. After finishing a job, diff --git a/docs/plugins/workflow.rst b/docs/plugins/workflow.rst index cb20ad806..c2a9ea26a 100644 --- a/docs/plugins/workflow.rst +++ b/docs/plugins/workflow.rst @@ -1,6 +1,6 @@ workflow ======== -Tags: +**Tags:** `tag/fort`, `tag/auto`, `tag/jobs` :dfhack-keybind:`workflow` :dfhack-keybind:`fix-job-postings` diff --git a/docs/plugins/xlsxreader.rst b/docs/plugins/xlsxreader.rst index d09b69644..123d9920a 100644 --- a/docs/plugins/xlsxreader.rst +++ b/docs/plugins/xlsxreader.rst @@ -1,5 +1,6 @@ xlsxreader ========== +**Tags:** `tag/dev` Provides a Lua API for reading xlsx files. diff --git a/docs/plugins/zone.rst b/docs/plugins/zone.rst index 5e01a12ac..9aa34d746 100644 --- a/docs/plugins/zone.rst +++ b/docs/plugins/zone.rst @@ -1,6 +1,6 @@ zone ==== -Tags: +**Tags:** `tag/fort`, `tag/productivity`, `tag/animals`, `tag/buildings` :dfhack-keybind:`zone` Manage activity zones, cages, and the animals therein. From b3a2a10caac918768bae3e1f41fb97bb3a375e8b Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 5 Aug 2022 22:05:52 -0700 Subject: [PATCH 439/854] clean up generate doc dirs with ninja clean --- .gitignore | 1 + CMakeLists.txt | 11 ++++++++++- docs/changelogs/.gitignore | 2 -- docs/sphinx_extensions/dfhack/changelog.py | 2 ++ 4 files changed, 13 insertions(+), 3 deletions(-) delete mode 100644 docs/changelogs/.gitignore diff --git a/.gitignore b/.gitignore index 0711d9b17..e91dcab3b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ docs/changelogs/ docs/html/ docs/pdf/ docs/pseudoxml/ +docs/tags/ docs/text/ docs/tools/ docs/xml/ diff --git a/CMakeLists.txt b/CMakeLists.txt index a793bcfbb..d8d90b007 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -473,7 +473,16 @@ if(BUILD_DOCS) ) set(SPHINX_OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/docs/html/.buildinfo") - set_source_files_properties(${SPHINX_OUTPUT} PROPERTIES GENERATED TRUE) + set_property( + DIRECTORY PROPERTY ADDITIONAL_CLEAN_FILES TRUE + "${CMAKE_CURRENT_SOURCE_DIR}/docs/changelogs" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/html" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/tags" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/text" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/tools" + "${CMAKE_BINARY_DIR}/docs/html" + "${CMAKE_BINARY_DIR}/docs/text" + ) add_custom_command(OUTPUT ${SPHINX_OUTPUT} COMMAND ${SPHINX_EXECUTABLE} -E -q -b html -d "${CMAKE_BINARY_DIR}/docs/html" diff --git a/docs/changelogs/.gitignore b/docs/changelogs/.gitignore deleted file mode 100644 index 90de5c70d..000000000 --- a/docs/changelogs/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.txt -*.rst diff --git a/docs/sphinx_extensions/dfhack/changelog.py b/docs/sphinx_extensions/dfhack/changelog.py index 629d6b6de..2f27590fd 100644 --- a/docs/sphinx_extensions/dfhack/changelog.py +++ b/docs/sphinx_extensions/dfhack/changelog.py @@ -238,6 +238,8 @@ def generate_changelog(all=False): consolidate_changelog(stable_entries) consolidate_changelog(dev_entries) + os.makedirs(os.path.join(DOCS_ROOT, 'changelogs'), mode=0o755, exist_ok=True) + print_changelog(versions, stable_entries, os.path.join(DOCS_ROOT, 'changelogs/news.rst')) print_changelog(versions, dev_entries, os.path.join(DOCS_ROOT, 'changelogs/news-dev.rst')) From 1a777257b43f86f547b2f7b586e28d797eb8227f Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 5 Aug 2022 22:06:02 -0700 Subject: [PATCH 440/854] add get_entry_types API method to helpdb --- library/lua/helpdb.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index a23002e57..bfca996eb 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -468,6 +468,10 @@ local function get_db_property(entry_name, property) textdb[entrydb[entry_name].text_entry][property] end +function ge_entry_types(entry) + return get_db_property(entry, 'entry_types') +end + -- returns the ~54 char summary blurb associated with the entry function get_entry_short_help(entry) return get_db_property(entry, 'short_help') @@ -637,9 +641,7 @@ function get_commands() end function is_builtin(command) - ensure_db() - return entrydb[command] and - get_db_property(command, 'entry_types')[ENTRY_TYPES.BUILTIN] + return is_entry(command) and get_entry_types(command)[ENTRY_TYPES.BUILTIN] end --------------------------------------------------------------------------- From 3e30b435fdb419a94b0db02f454a66235765a181 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 5 Aug 2022 22:20:58 -0700 Subject: [PATCH 441/854] clean up docs build deps --- CMakeLists.txt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d8d90b007..129fcb16b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -459,12 +459,19 @@ if(BUILD_DOCS) "${CMAKE_CURRENT_SOURCE_DIR}/changelog.txt" ) list(FILTER SPHINX_GLOB_RECURSE_DEPS - EXCLUDE REGEX "docs/_" + EXCLUDE REGEX "docs/changelogs" ) - file(GLOB_RECURSE SPHINX_SCRIPT_DEPS - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.lua" - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.rb" - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.txt" + list(FILTER SPHINX_GLOB_RECURSE_DEPS + EXCLUDE REGEX "docs/html" + ) + list(FILTER SPHINX_GLOB_RECURSE_DEPS + EXCLUDE REGEX "docs/tags" + ) + list(FILTER SPHINX_GLOB_RECURSE_DEPS + EXCLUDE REGEX "docs/text" + ) + list(FILTER SPHINX_GLOB_RECURSE_DEPS + EXCLUDE REGEX "docs/tools" ) set(SPHINX_DEPS ${SPHINX_GLOB_DEPS} ${SPHINX_GLOB_RECURSE_DEPS} ${SPHINX_SCRIPT_DEPS} "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt" From d04beb0e4a9b903fce241f15f2f1264701fc99a2 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 5 Aug 2022 22:21:14 -0700 Subject: [PATCH 442/854] remove -E for sphinx-build. ninja clean can do it. --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 129fcb16b..5a650269a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -492,13 +492,13 @@ if(BUILD_DOCS) ) add_custom_command(OUTPUT ${SPHINX_OUTPUT} COMMAND ${SPHINX_EXECUTABLE} - -E -q -b html -d "${CMAKE_BINARY_DIR}/docs/html" + -q -b html -d "${CMAKE_BINARY_DIR}/docs/html" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/docs/html" -w "${CMAKE_BINARY_DIR}/docs/html/_sphinx-warnings.txt" -j auto COMMAND ${SPHINX_EXECUTABLE} - -E -q -b text -d "${CMAKE_BINARY_DIR}/docs/text" + -q -b text -d "${CMAKE_BINARY_DIR}/docs/text" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/docs/text" -w "${CMAKE_BINARY_DIR}/docs/text/_sphinx-warnings.txt" From 625b4d439df84d0ad45dfc52795031e6d0ca9b2b Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 5 Aug 2022 22:31:39 -0700 Subject: [PATCH 443/854] fix typo in API function name --- library/lua/helpdb.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index bfca996eb..7bc95bc75 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -468,7 +468,7 @@ local function get_db_property(entry_name, property) textdb[entrydb[entry_name].text_entry][property] end -function ge_entry_types(entry) +function get_entry_types(entry) return get_db_property(entry, 'entry_types') end From 6700a8d9222a00ff91accd9336734124f569d2c8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 5 Aug 2022 22:31:51 -0700 Subject: [PATCH 444/854] add missing keybinds for filltraffic plugin --- docs/plugins/filltraffic.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/plugins/filltraffic.rst b/docs/plugins/filltraffic.rst index 5a3beaab7..acc4dabc8 100644 --- a/docs/plugins/filltraffic.rst +++ b/docs/plugins/filltraffic.rst @@ -4,7 +4,10 @@ filltraffic =========== **Tags:** `tag/fort`, `tag/productivity`, `tag/design`, `tag/map` -:dfhack-keybind:`` +:dfhack-keybind:`filltraffic` +:dfhack-keybind:`alltraffic` +:dfhack-keybind:`restrictice` +:dfhack-keybind:`restrictliquids` Usage: From 02a8f63ffc542b5d9f4f63b2bd7962ef31fcf095 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 6 Aug 2022 05:38:19 +0000 Subject: [PATCH 445/854] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a650269a..74bad7fa5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -489,7 +489,7 @@ if(BUILD_DOCS) "${CMAKE_CURRENT_SOURCE_DIR}/docs/tools" "${CMAKE_BINARY_DIR}/docs/html" "${CMAKE_BINARY_DIR}/docs/text" - ) + ) add_custom_command(OUTPUT ${SPHINX_OUTPUT} COMMAND ${SPHINX_EXECUTABLE} -q -b html -d "${CMAKE_BINARY_DIR}/docs/html" From c73200bf66dc4e93d6de6e572229e2d9c0cbad5a Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 6 Aug 2022 07:16:47 +0000 Subject: [PATCH 446/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 6a66be4fa..f37c0d022 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 6a66be4facd1d5a7b3ab4cb61c34c678d19e0795 +Subproject commit f37c0d022135ca10fb5375c4e56e11215bf208ae From ac175affbc2d2a5928a941239cba656bf886996c Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 12:22:27 -0400 Subject: [PATCH 447/854] Make renderer-msg draw somewhat more reliably From g_src (enabler.cpp: renderer::display()), either update_all() or update_tile() is called at least once per frame --- plugins/devel/renderer-msg.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/plugins/devel/renderer-msg.cpp b/plugins/devel/renderer-msg.cpp index e6a33114f..4d5da09a6 100644 --- a/plugins/devel/renderer-msg.cpp +++ b/plugins/devel/renderer-msg.cpp @@ -23,13 +23,22 @@ struct scdata { }; struct renderer_msg : public Renderer::renderer_wrap { - virtual void update_tile (int32_t x, int32_t y) { + virtual void update_tile(int32_t x, int32_t y) override { + draw_message(); + renderer_wrap::update_tile(x, y); + } + + virtual void update_all() override { + draw_message(); + renderer_wrap::update_all(); + } + + void draw_message() { static std::string str = std::string("DFHack: ") + plugin_name + " active"; - Screen::paintString(Screen::Pen(' ', 9, 0), 0, gps->dimy - 1, str); + Screen::paintString(Screen::Pen(' ', COLOR_LIGHTCYAN, COLOR_GREEN), 0, gps->dimy - 1, str); for (int32_t i = 0; i < gps->dimx; ++i) ((scdata*)screen)[i * gps->dimy + gps->dimy - 1].bg = 2; - renderer_wrap::update_tile(x, y); - }; + } }; DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) From 69a84c23c2da0190ee25a10332f31e29183a1c89 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 12:33:25 -0400 Subject: [PATCH 448/854] renderer-msg: draw less often suggested by Quietust --- plugins/devel/renderer-msg.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/plugins/devel/renderer-msg.cpp b/plugins/devel/renderer-msg.cpp index 4d5da09a6..1f26886c6 100644 --- a/plugins/devel/renderer-msg.cpp +++ b/plugins/devel/renderer-msg.cpp @@ -23,6 +23,8 @@ struct scdata { }; struct renderer_msg : public Renderer::renderer_wrap { + bool message_dirty = true; // force redraw when renderer is installed + virtual void update_tile(int32_t x, int32_t y) override { draw_message(); renderer_wrap::update_tile(x, y); @@ -33,11 +35,20 @@ struct renderer_msg : public Renderer::renderer_wrap { renderer_wrap::update_all(); } + virtual void render() override { + message_dirty = true; + renderer_wrap::render(); + } + void draw_message() { - static std::string str = std::string("DFHack: ") + plugin_name + " active"; - Screen::paintString(Screen::Pen(' ', COLOR_LIGHTCYAN, COLOR_GREEN), 0, gps->dimy - 1, str); - for (int32_t i = 0; i < gps->dimx; ++i) - ((scdata*)screen)[i * gps->dimy + gps->dimy - 1].bg = 2; + if (message_dirty) { + static std::string str = std::string("DFHack: ") + plugin_name + " active"; + Screen::paintString(Screen::Pen(' ', COLOR_LIGHTCYAN, COLOR_GREEN), 0, gps->dimy - 1, str); + for (int32_t i = 0; i < gps->dimx; ++i) + ((scdata*)screen)[i * gps->dimy + gps->dimy - 1].bg = 2; + + message_dirty = false; + } } }; From 9098914ce47471d8a368e07ae46ccad2074959b5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 22:47:38 -0400 Subject: [PATCH 449/854] Add --offline option to docs/build.py to disable image downloads --- conf.py | 16 ++++++++++++++++ docs/build.py | 10 +++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/conf.py b/conf.py index 08c2e6bd8..dd7be9792 100644 --- a/conf.py +++ b/conf.py @@ -24,6 +24,22 @@ import sphinx import sys +if os.environ.get('DFHACK_DOCS_BUILD_OFFLINE'): + # block attempted image downloads, particularly for the PDF builder + def request_disabled(*args, **kwargs): + raise RuntimeError('Offline build - network request blocked') + + import urllib3.util + urllib3.util.create_connection = request_disabled + + import urllib3.connection + urllib3.connection.HTTPConnection.connect = request_disabled + + import requests + requests.request = request_disabled + requests.get = request_disabled + + # -- Support :dfhack-keybind:`command` ------------------------------------ # this is a custom directive that pulls info from default keybindings diff --git a/docs/build.py b/docs/build.py index 582279bcb..6a63ed59e 100755 --- a/docs/build.py +++ b/docs/build.py @@ -63,6 +63,8 @@ def parse_args(source_args): help='Number of Sphinx threads to run [environment variable: JOBS; default: "auto"]') parser.add_argument('--debug', action='store_true', help='Log commands that are run, etc.') + parser.add_argument('--offline', action='store_true', + help='Disable network connections') args, forward_args = _parse_known_args(parser, source_args) # work around weirdness with list args @@ -79,6 +81,11 @@ if __name__ == '__main__': exit(1) args, forward_args = parse_args(sys.argv[1:]) + + sphinx_env = os.environ.copy() + if args.offline: + sphinx_env['DFHACK_DOCS_BUILD_OFFLINE'] = '1' + for format_name in args.format: command = [args.sphinx] + OUTPUT_FORMATS[format_name].args + ['-j', args.jobs] if args.clean: @@ -88,5 +95,6 @@ if __name__ == '__main__': if args.debug: print('Building:', format_name) print('Running:', command) - subprocess.call(command) + subprocess.run(command, check=True, env=sphinx_env) + print('') From 153fef934b612d58b2e1692ae1eb315205c121c2 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 6 Aug 2022 23:32:00 -0700 Subject: [PATCH 450/854] clean up Label tests --- test/library/gui/widgets.Label.lua | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/test/library/gui/widgets.Label.lua b/test/library/gui/widgets.Label.lua index 49a75a235..4e6222e3a 100644 --- a/test/library/gui/widgets.Label.lua +++ b/test/library/gui/widgets.Label.lua @@ -1,13 +1,6 @@ --- test -dhack/scripts/devel/tests -twidgets.Label - local gui = require('gui') local widgets = require('gui.widgets') -local xtest = {} -- use to temporarily disable tests (change `function xtest.somename` to `function xxtest.somename`) -local wait = function(n) - delay(n or 30) -- enable for debugging the tests -end - local fs = defclass(fs, gui.FramedScreen) fs.ATTRS = { frame_style = gui.GREY_LINE_FRAME, @@ -18,7 +11,7 @@ fs.ATTRS = { focus_path = 'test-framed-screen', } -function test.Label_correct_frame_body_with_scroll_icons() +function test.correct_frame_body_with_scroll_icons() local t = {} for i = 1, 12 do t[#t+1] = tostring(i) @@ -31,19 +24,15 @@ function test.Label_correct_frame_body_with_scroll_icons() view_id = 'text', frame_inset = 0, text = t, - --show_scroll_icons = 'right', }, } end local o = fs{} - --o:show() - --wait() expect.eq(o.subviews.text.frame_body.width, 9, "Label's frame_body.x2 and .width should be one smaller because of show_scroll_icons.") - --o:dismiss() end -function test.Label_correct_frame_body_with_few_text_lines() +function test.correct_frame_body_with_few_text_lines() local t = {} for i = 1, 10 do t[#t+1] = tostring(i) @@ -56,19 +45,15 @@ function test.Label_correct_frame_body_with_few_text_lines() view_id = 'text', frame_inset = 0, text = t, - --show_scroll_icons = 'right', }, } end local o = fs{} - --o:show() - --wait() expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.") - --o:dismiss() end -function test.Label_correct_frame_body_without_show_scroll_icons() +function test.correct_frame_body_without_show_scroll_icons() local t = {} for i = 1, 12 do t[#t+1] = tostring(i) @@ -87,8 +72,5 @@ function test.Label_correct_frame_body_without_show_scroll_icons() end local o = fs{} - --o:show() - --wait() expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.") - --o:dismiss() end From a8d0cc798036901a8ccbd7ba4c0b730e94787fce Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 6 Aug 2022 23:48:25 -0700 Subject: [PATCH 451/854] support scrolling by half pages in Label --- docs/Lua API.rst | 17 ++++++++--- library/lua/gui/widgets.lua | 18 ++++++++---- test/library/gui/widgets.Label.lua | 46 ++++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 12 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 94eaa1165..d436bdd5c 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3928,11 +3928,14 @@ It has the following attributes: :auto_width: Sets self.frame.w from the text width. :on_click: A callback called when the label is clicked (optional) :on_rclick: A callback called when the label is right-clicked (optional) -:scroll_keys: Specifies which keys the label should react to as a table. Default is ``STANDARDSCROLL`` (up or down arrows, page up or down). +:scroll_keys: Specifies which keys the label should react to as a table. The table should map + keys to the number of lines to scroll as positive or negative integers or one of the keywords + supported by the ``scroll`` method. The default is up/down arrows scrolling by one line and page + up/down scrolling by one page. :show_scroll_icons: Controls scroll icons' behaviour: ``false`` for no icons, ``'right'`` or ``'left'`` for - icons next to the text in an additional column (``frame_inset`` is adjusted to have ``.r`` or ``.l`` greater than ``0``), - ``nil`` same as ``'right'`` but changes ``frame_inset`` only if a scroll icon is actually necessary - (if ``getTextHeight()`` is greater than ``frame_body.height``). Default is ``nil``. + icons next to the text in an additional column (``frame_inset`` is adjusted to have ``.r`` or ``.l`` greater than ``0``), + ``nil`` same as ``'right'`` but changes ``frame_inset`` only if a scroll icon is actually necessary + (if ``getTextHeight()`` is greater than ``frame_body.height``). Default is ``nil``. :up_arrow_icon: The symbol for scroll up arrow. Default is ``string.char(24)`` (``↑``). :down_arrow_icon: The symbol for scroll down arrow. Default is ``string.char(25)`` (``↓``). :scroll_icon_pen: Specifies the pen for scroll icons. Default is ``COLOR_LIGHTCYAN``. @@ -4024,6 +4027,12 @@ The Label widget implements the following methods: Computes the width of the text. +* ``label:scroll(nlines)`` + + This method takes the number of lines to scroll as positive or negative + integers or one of the following keywords: ``+page``, ``-page``, + ``+halfpage``, or ``-halfpage``. + WrappedLabel class ------------------ diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 5765992af..3ac74bb52 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -652,6 +652,19 @@ function Label:onRenderFrame(dc, rect) end function Label:scroll(nlines) + if type(nlines) == 'string' then + if nlines == '+page' then + nlines = self.frame_body.height + elseif nlines == '-page' then + nlines = -self.frame_body.height + elseif nlines == '+halfpage' then + nlines = math.ceil(self.frame_body.height/2) + elseif nlines == '-halfpage' then + nlines = -math.ceil(self.frame_body.height/2) + else + error(('unhandled scroll keyword: "%s"'):format(nlines)) + end + end local n = self.start_line_num + nlines n = math.min(n, self:getTextHeight() - self.frame_body.height + 1) n = math.max(n, 1) @@ -668,11 +681,6 @@ function Label:onInput(keys) end for k,v in pairs(self.scroll_keys) do if keys[k] then - if v == '+page' then - v = self.frame_body.height - elseif v == '-page' then - v = -self.frame_body.height - end self:scroll(v) end end diff --git a/test/library/gui/widgets.Label.lua b/test/library/gui/widgets.Label.lua index 4e6222e3a..9a5d462fa 100644 --- a/test/library/gui/widgets.Label.lua +++ b/test/library/gui/widgets.Label.lua @@ -18,7 +18,7 @@ function test.correct_frame_body_with_scroll_icons() t[#t+1] = NEWLINE end - function fs:init(args) + function fs:init() self:addviews{ widgets.Label{ view_id = 'text', @@ -39,7 +39,7 @@ function test.correct_frame_body_with_few_text_lines() t[#t+1] = NEWLINE end - function fs:init(args) + function fs:init() self:addviews{ widgets.Label{ view_id = 'text', @@ -60,7 +60,7 @@ function test.correct_frame_body_without_show_scroll_icons() t[#t+1] = NEWLINE end - function fs:init(args) + function fs:init() self:addviews{ widgets.Label{ view_id = 'text', @@ -74,3 +74,43 @@ function test.correct_frame_body_without_show_scroll_icons() local o = fs{} expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.") end + +function test.scroll() + local t = {} + for i = 1, 12 do + t[#t+1] = tostring(i) + t[#t+1] = NEWLINE + end + + function fs:init() + self:addviews{ + widgets.Label{ + view_id = 'text', + frame_inset = 0, + text = t, + }, + } + end + + local o = fs{frame_height=3} + local txt = o.subviews.text + expect.eq(1, txt.start_line_num) + + txt:scroll(1) + expect.eq(2, txt.start_line_num) + txt:scroll('+page') + expect.eq(5, txt.start_line_num) + txt:scroll('+halfpage') + expect.eq(7, txt.start_line_num) + txt:scroll('-halfpage') + expect.eq(5, txt.start_line_num) + txt:scroll('-page') + expect.eq(2, txt.start_line_num) + txt:scroll(-1) + expect.eq(1, txt.start_line_num) + + txt:scroll(-1) + expect.eq(1, txt.start_line_num) + txt:scroll(100) + expect.eq(10, txt.start_line_num) +end From f78e4276f985ca532a265f2e477ed0f4a772b7f9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 7 Aug 2022 15:27:14 +0000 Subject: [PATCH 452/854] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- test/library/gui/widgets.Label.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/library/gui/widgets.Label.lua b/test/library/gui/widgets.Label.lua index 9a5d462fa..6b0097d1e 100644 --- a/test/library/gui/widgets.Label.lua +++ b/test/library/gui/widgets.Label.lua @@ -95,7 +95,7 @@ function test.scroll() local o = fs{frame_height=3} local txt = o.subviews.text expect.eq(1, txt.start_line_num) - + txt:scroll(1) expect.eq(2, txt.start_line_num) txt:scroll('+page') From ef56addb1476c3039757a312d11db405c159d0b5 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 7 Aug 2022 22:13:46 -0700 Subject: [PATCH 453/854] prep for new format; accept pipe as tag separator --- library/lua/helpdb.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 7bc95bc75..a6c635ee0 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -211,7 +211,7 @@ local function update_entry(entry, iterator, opts) ::continue:: end entry.tags = {} - for _,tag in ipairs(tags:split('[ ,]+')) do + for _,tag in ipairs(tags:split('[ ,|]+')) do entry.tags[tag] = true end if #lines > 0 then From 8930f30b72c2c4d3486a70ab05ee350c55295ef9 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 7 Aug 2022 22:36:47 -0700 Subject: [PATCH 454/854] remove unuseful command from automelt --- plugins/automelt.cpp | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/plugins/automelt.cpp b/plugins/automelt.cpp index 852b324d6..4bbb727d7 100644 --- a/plugins/automelt.cpp +++ b/plugins/automelt.cpp @@ -250,19 +250,6 @@ IMPLEMENT_VMETHOD_INTERPOSE(melt_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(melt_hook, render); -static command_result automelt_cmd(color_ostream &out, vector & parameters) -{ - if (!parameters.empty()) - { - if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v') - { - out << "Automelt" << endl << "Version: " << PLUGIN_VERSION << endl; - } - } - - return CR_OK; -} - DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { switch (event) @@ -296,15 +283,12 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back( - PluginCommand( - "automelt", "Automatically melt metal items in marked stockpiles.", - automelt_cmd, false, "")); - return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { + // ensure we disengage our hooks + plugin_enable(out, false); return CR_OK; } From 22de8f61397eb3a2be38f291ef22cb8f4598d07e Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 7 Aug 2022 22:50:47 -0700 Subject: [PATCH 455/854] add secondary hotkey for gui/launcher --- data/init/dfhack.keybindings.init | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/init/dfhack.keybindings.init b/data/init/dfhack.keybindings.init index f38840e06..c8785eba7 100644 --- a/data/init/dfhack.keybindings.init +++ b/data/init/dfhack.keybindings.init @@ -8,7 +8,9 @@ # Generic dwarfmode bindings # ############################## +# the GUI command launcher (two bindings since some keyboards don't have `) keybinding add ` gui/launcher +keybinding add Ctrl-Shift-D gui/launcher # show all current key bindings keybinding add Ctrl-F1 hotkeys @@ -42,7 +44,7 @@ keybinding add Ctrl-K autodump-destroy-item # quicksave, only in main dwarfmode screen and menu page keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave -# gui/quickfort script - apply pre-made blueprints to the map +# apply blueprints to the map (Alt-F for compatibility with LNP Quickfort) keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort keybinding add Alt-F@dwarfmode gui/quickfort From 7274a8cd2a08f84dec341e5906f12767f9789790 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 7 Aug 2022 23:35:00 -0700 Subject: [PATCH 456/854] use docs/build.py to do the docs build and add sphinx extension python files to the build deps --- CMakeLists.txt | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 74bad7fa5..1f11dc93a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -457,6 +457,7 @@ if(BUILD_DOCS) file(GLOB_RECURSE SPHINX_GLOB_RECURSE_DEPS "${CMAKE_CURRENT_SOURCE_DIR}/*.rst" "${CMAKE_CURRENT_SOURCE_DIR}/changelog.txt" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/*py" ) list(FILTER SPHINX_GLOB_RECURSE_DEPS EXCLUDE REGEX "docs/changelogs" @@ -476,7 +477,6 @@ if(BUILD_DOCS) set(SPHINX_DEPS ${SPHINX_GLOB_DEPS} ${SPHINX_GLOB_RECURSE_DEPS} ${SPHINX_SCRIPT_DEPS} "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt" "${CMAKE_CURRENT_SOURCE_DIR}/conf.py" - "${CMAKE_CURRENT_SOURCE_DIR}/docs/gen_changelog.py" ) set(SPHINX_OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/docs/html/.buildinfo") @@ -491,18 +491,8 @@ if(BUILD_DOCS) "${CMAKE_BINARY_DIR}/docs/text" ) add_custom_command(OUTPUT ${SPHINX_OUTPUT} - COMMAND ${SPHINX_EXECUTABLE} - -q -b html -d "${CMAKE_BINARY_DIR}/docs/html" - "${CMAKE_CURRENT_SOURCE_DIR}" - "${CMAKE_CURRENT_SOURCE_DIR}/docs/html" - -w "${CMAKE_BINARY_DIR}/docs/html/_sphinx-warnings.txt" - -j auto - COMMAND ${SPHINX_EXECUTABLE} - -q -b text -d "${CMAKE_BINARY_DIR}/docs/text" - "${CMAKE_CURRENT_SOURCE_DIR}" - "${CMAKE_CURRENT_SOURCE_DIR}/docs/text" - -w "${CMAKE_BINARY_DIR}/docs/text/_sphinx-warnings.txt" - -j auto + COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/docs/build.py" + html text --sphinx="${SPHINX_EXECUTABLE}" -- -q DEPENDS ${SPHINX_DEPS} COMMENT "Building documentation with Sphinx" ) From 8f332c5925a9a4059c6868c7e96a4911f50c798f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Aug 2022 06:39:11 +0000 Subject: [PATCH 457/854] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f11dc93a..c341735c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -492,7 +492,7 @@ if(BUILD_DOCS) ) add_custom_command(OUTPUT ${SPHINX_OUTPUT} COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/docs/build.py" - html text --sphinx="${SPHINX_EXECUTABLE}" -- -q + html text --sphinx="${SPHINX_EXECUTABLE}" -- -q DEPENDS ${SPHINX_DEPS} COMMENT "Building documentation with Sphinx" ) From c44c8721c9ec2a2040edbda97c610afc405436a0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2022 02:17:21 -0400 Subject: [PATCH 458/854] Initial attempt at dfhack-tool directive Doesn't appear to produce headings that can be used as link targets... --- conf.py | 1 + docs/plugins/3dveins.rst | 3 ++ docs/sphinx_extensions/dfhack/tool_docs.py | 52 ++++++++++++++++++++++ docs/sphinx_extensions/dfhack/util.py | 13 ++++++ 4 files changed, 69 insertions(+) create mode 100644 docs/sphinx_extensions/dfhack/tool_docs.py diff --git a/conf.py b/conf.py index dd7be9792..e8ff8392b 100644 --- a/conf.py +++ b/conf.py @@ -237,6 +237,7 @@ extensions = [ 'sphinx.ext.extlinks', 'dfhack.changelog', 'dfhack.lexer', + 'dfhack.tool_docs', ] sphinx_major_version = sphinx.version_info[0] diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 680d44c6e..9cd678709 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -3,6 +3,9 @@ **Tags:** `tag/fort`, `tag/mod`, `tag/map` :dfhack-keybind:`3dveins` +.. dfhack-tool:: 3dveins + :tags: foo, bar + :index:`Rewrite layer veins to expand in 3D space. <3dveins; Rewrite layer veins to expand in 3D space.>` Existing, flat veins are removed and new 3D veins that naturally span z-levels are generated in diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py new file mode 100644 index 000000000..ac06a6576 --- /dev/null +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -0,0 +1,52 @@ +# useful references: +# https://www.sphinx-doc.org/en/master/extdev/appapi.html +# https://www.sphinx-doc.org/en/master/development/tutorials/recipe.html +# https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rst-directives + +import docutils.nodes as nodes +# import docutils.parsers.rst.directives as rst_directives +import sphinx +import sphinx.directives + +import dfhack.util + +class DFHackToolDirective(sphinx.directives.ObjectDescription): + has_content = False + required_arguments = 1 + option_spec = { + 'tags': dfhack.util.directive_arg_str_list, + } + + def run(self): + tool_name = self.get_signatures()[0] + + tag_nodes = [nodes.strong(text='Tags: ')] + for tag in self.options['tags']: + tag_nodes += [ + nodes.literal(tag, tag), + nodes.inline(text=' | '), + ] + tag_nodes.pop() + + return [ + nodes.title(text=tool_name), + nodes.paragraph('', '', *tag_nodes), + ] + + # simpler but always renders as "inline code" (and less customizable) + # def handle_signature(self, sig, signode): + # signode += addnodes.desc_name(text=sig) + # return sig + + +def register(app): + app.add_directive('dfhack-tool', DFHackToolDirective) + +def setup(app): + app.connect('builder-inited', register) + + return { + 'version': '0.1', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/docs/sphinx_extensions/dfhack/util.py b/docs/sphinx_extensions/dfhack/util.py index 71a432da4..61f38b043 100644 --- a/docs/sphinx_extensions/dfhack/util.py +++ b/docs/sphinx_extensions/dfhack/util.py @@ -5,3 +5,16 @@ DOCS_ROOT = os.path.join(DFHACK_ROOT, 'docs') if not os.path.isdir(DOCS_ROOT): raise ReferenceError('docs root not found: %s' % DOCS_ROOT) + +# directive argument helpers (supplementing docutils.parsers.rst.directives) +def directive_arg_str_list(argument): + """ + Converts a space- or comma-separated list of values into a Python list + of strings. + (Directive option conversion function.) + """ + if ',' in argument: + entries = argument.split(',') + else: + entries = argument.split() + return [entry.strip() for entry in entries] From d96260556eb7e92d3f0b3f61defdecbe38892a41 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2022 03:18:51 -0400 Subject: [PATCH 459/854] Make title visible by putting it in its own section --- docs/plugins/3dveins.rst | 6 +++--- docs/sphinx_extensions/dfhack/tool_docs.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 9cd678709..7ae2324eb 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -1,10 +1,10 @@ 3dveins ======= -**Tags:** `tag/fort`, `tag/mod`, `tag/map` -:dfhack-keybind:`3dveins` .. dfhack-tool:: 3dveins - :tags: foo, bar + :tags: fort, mod, map + +:dfhack-keybind:`3dveins` :index:`Rewrite layer veins to expand in 3D space. <3dveins; Rewrite layer veins to expand in 3D space.>` Existing, flat veins diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index ac06a6576..dcda81d8d 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -29,7 +29,7 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): tag_nodes.pop() return [ - nodes.title(text=tool_name), + nodes.section('', nodes.title(text=tool_name)), nodes.paragraph('', '', *tag_nodes), ] From 89a88e94a9794bc488114c73e670de0080e539c0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 27 Jul 2022 22:02:08 -0400 Subject: [PATCH 460/854] Allow empty :tags:, give section a name to prevent errors --- docs/sphinx_extensions/dfhack/tool_docs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index dcda81d8d..79516e0d8 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -21,7 +21,7 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): tool_name = self.get_signatures()[0] tag_nodes = [nodes.strong(text='Tags: ')] - for tag in self.options['tags']: + for tag in self.options.get('tags', []): tag_nodes += [ nodes.literal(tag, tag), nodes.inline(text=' | '), @@ -29,7 +29,7 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): tag_nodes.pop() return [ - nodes.section('', nodes.title(text=tool_name)), + nodes.section('', nodes.title(text=tool_name), ids=[tool_name]), nodes.paragraph('', '', *tag_nodes), ] From e47c681e9c252f0f4599f5c2f7b41ea13e4f05f3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 4 Aug 2022 02:07:52 -0400 Subject: [PATCH 461/854] Use write_file_if_changed() in changelog.py Speeds up incremental builds significantly --- conf.py | 25 ++++------------------ docs/sphinx_extensions/dfhack/changelog.py | 4 ++-- docs/sphinx_extensions/dfhack/util.py | 20 +++++++++++++++++ 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/conf.py b/conf.py index e8ff8392b..49d005f46 100644 --- a/conf.py +++ b/conf.py @@ -14,9 +14,7 @@ serve to show the default. # pylint:disable=redefined-builtin -import contextlib import datetime -import io import os import re import shlex # pylint:disable=unused-import @@ -46,6 +44,10 @@ if os.environ.get('DFHACK_DOCS_BUILD_OFFLINE'): from docutils import nodes from docutils.parsers.rst import roles +sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'docs', 'sphinx_extensions')) +from dfhack.util import write_file_if_changed + + def get_keybinds(root, files, keybindings): """Add keybindings in the specified files to the given keybindings dict. @@ -145,23 +147,6 @@ def get_tags(): return tags -@contextlib.contextmanager -def write_file_if_changed(path): - with io.StringIO() as buffer: - yield buffer - new_contents = buffer.getvalue() - - try: - with open(path, 'r') as infile: - old_contents = infile.read() - except IOError: - old_contents = None - - if old_contents != new_contents: - with open(path, 'w') as outfile: - outfile.write(new_contents) - - def generate_tag_indices(): os.makedirs('docs/tags', mode=0o755, exist_ok=True) with write_file_if_changed('docs/tags/index.rst') as topidx: @@ -225,8 +210,6 @@ generate_tag_indices() # -- General configuration ------------------------------------------------ -sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'docs', 'sphinx_extensions')) - # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.8' diff --git a/docs/sphinx_extensions/dfhack/changelog.py b/docs/sphinx_extensions/dfhack/changelog.py index 2f27590fd..0cd732988 100644 --- a/docs/sphinx_extensions/dfhack/changelog.py +++ b/docs/sphinx_extensions/dfhack/changelog.py @@ -6,7 +6,7 @@ import sys from sphinx.errors import ExtensionError, SphinxError, SphinxWarning -from dfhack.util import DFHACK_ROOT, DOCS_ROOT +from dfhack.util import DFHACK_ROOT, DOCS_ROOT, write_file_if_changed CHANGELOG_PATHS = ( 'docs/changelog.txt', @@ -172,7 +172,7 @@ def consolidate_changelog(all_entries): def print_changelog(versions, all_entries, path, replace=True, prefix=''): # all_entries: version -> section -> entry - with open(path, 'w') as f: + with write_file_if_changed(path) as f: def write(line): if replace: line = replace_text(line, REPLACEMENTS) diff --git a/docs/sphinx_extensions/dfhack/util.py b/docs/sphinx_extensions/dfhack/util.py index 61f38b043..91f0accbe 100644 --- a/docs/sphinx_extensions/dfhack/util.py +++ b/docs/sphinx_extensions/dfhack/util.py @@ -1,3 +1,5 @@ +import contextlib +import io import os DFHACK_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) @@ -6,6 +8,24 @@ DOCS_ROOT = os.path.join(DFHACK_ROOT, 'docs') if not os.path.isdir(DOCS_ROOT): raise ReferenceError('docs root not found: %s' % DOCS_ROOT) + +@contextlib.contextmanager +def write_file_if_changed(path): + with io.StringIO() as buffer: + yield buffer + new_contents = buffer.getvalue() + + try: + with open(path, 'r') as infile: + old_contents = infile.read() + except IOError: + old_contents = None + + if old_contents != new_contents: + with open(path, 'w') as outfile: + outfile.write(new_contents) + + # directive argument helpers (supplementing docutils.parsers.rst.directives) def directive_arg_str_list(argument): """ From de5f4d356679db170f823d806c81b2b1f3b2ef18 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 16:24:56 -0400 Subject: [PATCH 462/854] Default to document basename in dfhack-tool directive --- docs/plugins/3dveins.rst | 2 +- docs/sphinx_extensions/dfhack/tool_docs.py | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 7ae2324eb..f1149eb98 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -1,7 +1,7 @@ 3dveins ======= -.. dfhack-tool:: 3dveins +.. dfhack-tool:: :tags: fort, mod, map :dfhack-keybind:`3dveins` diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 79516e0d8..fd6ed0946 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -12,13 +12,16 @@ import dfhack.util class DFHackToolDirective(sphinx.directives.ObjectDescription): has_content = False - required_arguments = 1 + required_arguments = 0 option_spec = { 'tags': dfhack.util.directive_arg_str_list, } def run(self): - tool_name = self.get_signatures()[0] + if self.arguments: + tool_name = self.arguments[0] + else: + tool_name = self.env.docname.split('/')[-1] tag_nodes = [nodes.strong(text='Tags: ')] for tag in self.options.get('tags', []): @@ -33,11 +36,6 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): nodes.paragraph('', '', *tag_nodes), ] - # simpler but always renders as "inline code" (and less customizable) - # def handle_signature(self, sig, signode): - # signode += addnodes.desc_name(text=sig) - # return sig - def register(app): app.add_directive('dfhack-tool', DFHackToolDirective) From bb2ca0cc1666c8a33ef3dcfb4f11073c4830b746 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 16:59:45 -0400 Subject: [PATCH 463/854] Render dfhack-tool as admonition Getting a section header integrated is complicated, so might as well emulate Mediawiki with a box-like element instead --- docs/sphinx_extensions/dfhack/tool_docs.py | 9 +++++++-- docs/styles/dfhack.css | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index fd6ed0946..809bd48b8 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -32,8 +32,13 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): tag_nodes.pop() return [ - nodes.section('', nodes.title(text=tool_name), ids=[tool_name]), - nodes.paragraph('', '', *tag_nodes), + nodes.admonition('', *[ + nodes.paragraph('', '', *[ + nodes.strong('', 'Tool: '), + nodes.inline('', tool_name), + ]), + nodes.paragraph('', '', *tag_nodes), + ], classes=['dfhack-tool-summary']), ] diff --git a/docs/styles/dfhack.css b/docs/styles/dfhack.css index 9b6e523ef..e24ce1c67 100644 --- a/docs/styles/dfhack.css +++ b/docs/styles/dfhack.css @@ -60,3 +60,8 @@ div.body { span.pre { overflow-wrap: break-word; } + +.dfhack-tool-summary p { + margin-top: 0; + margin-bottom: 0.5em; +} From 12b3363b2ca1c8417c2a1fb20f5806709bc58f76 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 17:26:33 -0400 Subject: [PATCH 464/854] Make dfhack-tool tags link to tag descriptions --- docs/sphinx_extensions/dfhack/tool_docs.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 809bd48b8..65da95181 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -6,6 +6,7 @@ import docutils.nodes as nodes # import docutils.parsers.rst.directives as rst_directives import sphinx +import sphinx.addnodes as addnodes import sphinx.directives import dfhack.util @@ -26,7 +27,13 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): tag_nodes = [nodes.strong(text='Tags: ')] for tag in self.options.get('tags', []): tag_nodes += [ - nodes.literal(tag, tag), + addnodes.pending_xref(tag, nodes.inline(text=tag), **{ + 'reftype': 'ref', + 'refdomain': 'std', + 'reftarget': 'tag/' + tag, + 'refexplicit': False, + 'refwarn': True, + }), nodes.inline(text=' | '), ] tag_nodes.pop() From d19ffa18060807dd58ede04e12cb54867d156950 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 23:08:51 -0400 Subject: [PATCH 465/854] Add stub dfhack-command directive, refactor to support --- docs/plugins/3dveins.rst | 2 + docs/sphinx_extensions/dfhack/tool_docs.py | 49 +++++++++++++++------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index f1149eb98..86a00b51c 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -4,6 +4,8 @@ .. dfhack-tool:: :tags: fort, mod, map +.. dfhack-command:: + :dfhack-keybind:`3dveins` :index:`Rewrite layer veins to expand in 3D space. diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 65da95181..9100375b3 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -11,19 +11,37 @@ import sphinx.directives import dfhack.util -class DFHackToolDirective(sphinx.directives.ObjectDescription): +class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): has_content = False required_arguments = 0 - option_spec = { - 'tags': dfhack.util.directive_arg_str_list, - } - def run(self): + def get_name_or_docname(self): if self.arguments: - tool_name = self.arguments[0] + return self.arguments[0] else: - tool_name = self.env.docname.split('/')[-1] + return self.env.docname.split('/')[-1] + + def make_labeled_paragraph(self, label, content): + return nodes.paragraph('', '', *[ + nodes.strong('', '{}: '.format(label)), + nodes.inline('', content), + ]) + + def make_nodes(self): + raise NotImplementedError + + def run(self): + return [ + nodes.admonition('', *self.make_nodes(), classes=['dfhack-tool-summary']), + ] + +class DFHackToolDirective(DFHackToolDirectiveBase): + option_spec = { + 'tags': dfhack.util.directive_arg_str_list, + } + + def make_nodes(self): tag_nodes = [nodes.strong(text='Tags: ')] for tag in self.options.get('tags', []): tag_nodes += [ @@ -39,18 +57,21 @@ class DFHackToolDirective(sphinx.directives.ObjectDescription): tag_nodes.pop() return [ - nodes.admonition('', *[ - nodes.paragraph('', '', *[ - nodes.strong('', 'Tool: '), - nodes.inline('', tool_name), - ]), - nodes.paragraph('', '', *tag_nodes), - ], classes=['dfhack-tool-summary']), + self.make_labeled_paragraph('Tool', self.get_name_or_docname()), + nodes.paragraph('', '', *tag_nodes), + ] + + +class DFHackCommandDirective(DFHackToolDirectiveBase): + def make_nodes(self): + return [ + self.make_labeled_paragraph('Command', self.get_name_or_docname()), ] def register(app): app.add_directive('dfhack-tool', DFHackToolDirective) + app.add_directive('dfhack-command', DFHackCommandDirective) def setup(app): app.connect('builder-inited', register) From b3d79f87cbb77e49d688400b6d588cd1f6b94f7e Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 23:12:26 -0400 Subject: [PATCH 466/854] Fix optional name override --- docs/sphinx_extensions/dfhack/tool_docs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 9100375b3..4911bd454 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -14,6 +14,7 @@ import dfhack.util class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): has_content = False required_arguments = 0 + optional_arguments = 1 def get_name_or_docname(self): if self.arguments: From 39e928845879cea0f2ed9461f22dcde1403ad081 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 23:11:12 -0400 Subject: [PATCH 467/854] Render commands as literals --- docs/sphinx_extensions/dfhack/tool_docs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 4911bd454..35e4de700 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -22,10 +22,10 @@ class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): else: return self.env.docname.split('/')[-1] - def make_labeled_paragraph(self, label, content): + def make_labeled_paragraph(self, label, content, label_class=nodes.strong, content_class=nodes.inline): return nodes.paragraph('', '', *[ - nodes.strong('', '{}: '.format(label)), - nodes.inline('', content), + label_class('', '{}: '.format(label)), + content_class('', content), ]) def make_nodes(self): @@ -66,7 +66,7 @@ class DFHackToolDirective(DFHackToolDirectiveBase): class DFHackCommandDirective(DFHackToolDirectiveBase): def make_nodes(self): return [ - self.make_labeled_paragraph('Command', self.get_name_or_docname()), + self.make_labeled_paragraph('Command', self.get_name_or_docname(), content_class=nodes.literal), ] From 5ef36d210fae15c4715d87680421f9e1559f6f65 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 7 Aug 2022 01:03:15 -0400 Subject: [PATCH 468/854] Render implicit dfhack-command alongside dfhack-tool unless :no-command: is passed --- docs/plugins/3dveins.rst | 2 -- docs/sphinx_extensions/dfhack/tool_docs.py | 28 +++++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 86a00b51c..f1149eb98 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -4,8 +4,6 @@ .. dfhack-tool:: :tags: fort, mod, map -.. dfhack-command:: - :dfhack-keybind:`3dveins` :index:`Rewrite layer veins to expand in 3D space. diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 35e4de700..28876c36a 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -3,8 +3,10 @@ # https://www.sphinx-doc.org/en/master/development/tutorials/recipe.html # https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rst-directives +from typing import List + import docutils.nodes as nodes -# import docutils.parsers.rst.directives as rst_directives +import docutils.parsers.rst.directives as rst_directives import sphinx import sphinx.addnodes as addnodes import sphinx.directives @@ -22,27 +24,31 @@ class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): else: return self.env.docname.split('/')[-1] - def make_labeled_paragraph(self, label, content, label_class=nodes.strong, content_class=nodes.inline): + @staticmethod + def make_labeled_paragraph(label, content, label_class=nodes.strong, content_class=nodes.inline) -> nodes.paragraph: return nodes.paragraph('', '', *[ label_class('', '{}: '.format(label)), content_class('', content), ]) - def make_nodes(self): + @staticmethod + def wrap_box(*children: List[nodes.Node]) -> nodes.Admonition: + return nodes.admonition('', *children, classes=['dfhack-tool-summary']) + + def render_content(self) -> List[nodes.Node]: raise NotImplementedError def run(self): - return [ - nodes.admonition('', *self.make_nodes(), classes=['dfhack-tool-summary']), - ] + return [self.wrap_box(*self.render_content())] class DFHackToolDirective(DFHackToolDirectiveBase): option_spec = { 'tags': dfhack.util.directive_arg_str_list, + 'no-command': rst_directives.flag, } - def make_nodes(self): + def render_content(self) -> List[nodes.Node]: tag_nodes = [nodes.strong(text='Tags: ')] for tag in self.options.get('tags', []): tag_nodes += [ @@ -62,9 +68,15 @@ class DFHackToolDirective(DFHackToolDirectiveBase): nodes.paragraph('', '', *tag_nodes), ] + def run(self): + out = DFHackToolDirectiveBase.run(self) + if 'no-command' not in self.options: + out += [self.wrap_box(*DFHackCommandDirective.render_content(self))] + return out + class DFHackCommandDirective(DFHackToolDirectiveBase): - def make_nodes(self): + def render_content(self) -> List[nodes.Node]: return [ self.make_labeled_paragraph('Command', self.get_name_or_docname(), content_class=nodes.literal), ] From ed95db27f5bd7b13e556fc2ca15b4f68f38cdcb7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 7 Aug 2022 01:37:14 -0400 Subject: [PATCH 469/854] Move dfhack-keybind role to tool_docs.py and call from dfhack-command --- conf.py | 67 +--------------------- docs/sphinx_extensions/dfhack/tool_docs.py | 66 ++++++++++++++++++++- 2 files changed, 67 insertions(+), 66 deletions(-) diff --git a/conf.py b/conf.py index 49d005f46..775f94b68 100644 --- a/conf.py +++ b/conf.py @@ -21,6 +21,8 @@ import shlex # pylint:disable=unused-import import sphinx import sys +sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'docs', 'sphinx_extensions')) +from dfhack.util import write_file_if_changed if os.environ.get('DFHACK_DOCS_BUILD_OFFLINE'): # block attempted image downloads, particularly for the PDF builder @@ -38,71 +40,6 @@ if os.environ.get('DFHACK_DOCS_BUILD_OFFLINE'): requests.get = request_disabled -# -- Support :dfhack-keybind:`command` ------------------------------------ -# this is a custom directive that pulls info from default keybindings - -from docutils import nodes -from docutils.parsers.rst import roles - -sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'docs', 'sphinx_extensions')) -from dfhack.util import write_file_if_changed - - -def get_keybinds(root, files, keybindings): - """Add keybindings in the specified files to the - given keybindings dict. - """ - for file in files: - with open(os.path.join(root, file)) as f: - lines = [l.replace('keybinding add', '').strip() for l in f.readlines() - if l.startswith('keybinding add')] - for k in lines: - first, command = k.split(' ', 1) - bind, context = (first.split('@') + [''])[:2] - if ' ' not in command: - command = command.replace('"', '') - tool = command.split(' ')[0].replace('"', '') - keybindings[tool] = keybindings.get(tool, []) + [ - (command, bind.split('-'), context)] - -def get_all_keybinds(root_dir): - """Get the implemented keybinds, and return a dict of - {tool: [(full_command, keybinding, context), ...]}. - """ - keybindings = dict() - for root, _, files in os.walk(root_dir): - get_keybinds(root, files, keybindings) - return keybindings - -KEYBINDS = get_all_keybinds('data/init') - - -# pylint:disable=unused-argument,dangerous-default-value,too-many-arguments -def dfhack_keybind_role_func(role, rawtext, text, lineno, inliner, - options={}, content=[]): - """Custom role parser for DFHack default keybinds.""" - roles.set_classes(options) - if text not in KEYBINDS: - return [], [] - newnode = nodes.paragraph() - for cmd, key, ctx in KEYBINDS[text]: - n = nodes.paragraph() - newnode += n - n += nodes.strong('Keybinding:', 'Keybinding:') - n += nodes.inline(' ', ' ') - for k in key: - n += nodes.inline(k, k, classes=['kbd']) - if cmd != text: - n += nodes.inline(' -> ', ' -> ') - n += nodes.literal(cmd, cmd, classes=['guilabel']) - if ctx: - n += nodes.inline(' in ', ' in ') - n += nodes.literal(ctx, ctx) - return [newnode], [] - - -roles.register_canonical_role('dfhack-keybind', dfhack_keybind_role_func) - # -- Autodoc for DFhack plugins and scripts ------------------------------- def doc_dir(dirname, files, prefix): diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 28876c36a..d1bdec8bf 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -3,6 +3,7 @@ # https://www.sphinx-doc.org/en/master/development/tutorials/recipe.html # https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rst-directives +import os from typing import List import docutils.nodes as nodes @@ -13,6 +14,63 @@ import sphinx.directives import dfhack.util + +_KEYBINDS = {} + +def scan_keybinds(root, files, keybindings): + """Add keybindings in the specified files to the + given keybindings dict. + """ + for file in files: + with open(os.path.join(root, file)) as f: + lines = [l.replace('keybinding add', '').strip() for l in f.readlines() + if l.startswith('keybinding add')] + for k in lines: + first, command = k.split(' ', 1) + bind, context = (first.split('@') + [''])[:2] + if ' ' not in command: + command = command.replace('"', '') + tool = command.split(' ')[0].replace('"', '') + keybindings[tool] = keybindings.get(tool, []) + [ + (command, bind.split('-'), context)] + +def scan_all_keybinds(root_dir): + """Get the implemented keybinds, and return a dict of + {tool: [(full_command, keybinding, context), ...]}. + """ + keybindings = dict() + for root, _, files in os.walk(root_dir): + scan_keybinds(root, files, keybindings) + return keybindings + + +def render_dfhack_keybind(command) -> List[nodes.paragraph]: + if command not in _KEYBINDS: + return [] + newnode = nodes.paragraph() + for keycmd, key, ctx in _KEYBINDS[command]: + n = nodes.paragraph() + newnode += n + n += nodes.strong('Keybinding:', 'Keybinding:') + n += nodes.inline(' ', ' ') + for k in key: + n += nodes.inline(k, k, classes=['kbd']) + if keycmd != command: + n += nodes.inline(' -> ', ' -> ') + n += nodes.literal(keycmd, keycmd, classes=['guilabel']) + if ctx: + n += nodes.inline(' in ', ' in ') + n += nodes.literal(ctx, ctx) + return [newnode] + + +# pylint:disable=unused-argument,dangerous-default-value,too-many-arguments +def dfhack_keybind_role(role, rawtext, text, lineno, inliner, + options={}, content=[]): + """Custom role parser for DFHack default keybinds.""" + return render_dfhack_keybind(text), [] + + class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): has_content = False required_arguments = 0 @@ -77,14 +135,20 @@ class DFHackToolDirective(DFHackToolDirectiveBase): class DFHackCommandDirective(DFHackToolDirectiveBase): def render_content(self) -> List[nodes.Node]: + command = self.get_name_or_docname() return [ - self.make_labeled_paragraph('Command', self.get_name_or_docname(), content_class=nodes.literal), + self.make_labeled_paragraph('Command', command, content_class=nodes.literal), + *render_dfhack_keybind(command), ] def register(app): app.add_directive('dfhack-tool', DFHackToolDirective) app.add_directive('dfhack-command', DFHackCommandDirective) + app.add_role('dfhack-keybind', dfhack_keybind_role) + + _KEYBINDS.update(scan_all_keybinds(os.path.join(dfhack.util.DFHACK_ROOT, 'data', 'init'))) + def setup(app): app.connect('builder-inited', register) From 5a14992aca93e896c334980ae0b104128508d0bd Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 7 Aug 2022 01:44:02 -0400 Subject: [PATCH 470/854] Use new directives for a few plugins --- docs/plugins/3dveins.rst | 2 -- docs/plugins/autodump.rst | 11 +++++++---- docs/plugins/autogems.rst | 8 ++++++-- docs/plugins/automelt.rst | 5 ++++- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index f1149eb98..b14f6145d 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -4,8 +4,6 @@ .. dfhack-tool:: :tags: fort, mod, map -:dfhack-keybind:`3dveins` - :index:`Rewrite layer veins to expand in 3D space. <3dveins; Rewrite layer veins to expand in 3D space.>` Existing, flat veins are removed and new 3D veins that naturally span z-levels are generated in diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst index 53ed7f1ba..dc60946b1 100644 --- a/docs/plugins/autodump.rst +++ b/docs/plugins/autodump.rst @@ -1,9 +1,12 @@ autodump ======== -**Tags:** `tag/fort`, `tag/auto`, `tag/fps`, `tag/items`, `tag/stockpiles` -:dfhack-keybind:`autodump` -:dfhack-keybind:`autodump-destroy-here` -:dfhack-keybind:`autodump-destroy-item` + +.. dfhack-tool:: + :tags: fort, auto, fps, items, stockpiles + +.. dfhack-command:: autodump-destroy-here + +.. dfhack-command:: autodump-destroy-item :index:`Automatically set items in a stockpile to be dumped. ` When diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index bcff93ba6..3ceed0bec 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -1,7 +1,11 @@ autogems ======== -**Tags:** `tag/fort`, `tag/auto`, `tag/jobs` -:dfhack-keybind:`autogems-reload` + +.. dfhack-tool:: autogems + :tags: fort, auto, jobs + :no-command: + +.. dfhack-command:: autogems-reload :index:`Automatically cut rough gems. ` This plugin periodically scans your stocks of rough gems and creates manager diff --git a/docs/plugins/automelt.rst b/docs/plugins/automelt.rst index 190bffc06..0ca9f82e9 100644 --- a/docs/plugins/automelt.rst +++ b/docs/plugins/automelt.rst @@ -1,6 +1,9 @@ automelt ======== -**Tags:** `tag/fort`, `tag/auto`, `tag/items`, `tag/stockpiles` + +.. dfhack-tool:: + :tags: fort, auto, items, stockpiles + :no-command: :index:`Quickly designate items to be melted. ` When `enabled `, this From 7651d301d244033fc392eafef2985f245e9e4dd5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 7 Aug 2022 01:51:38 -0400 Subject: [PATCH 471/854] Remove extra paragraph around keybindings --- docs/sphinx_extensions/dfhack/tool_docs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index d1bdec8bf..920bef98e 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -45,12 +45,11 @@ def scan_all_keybinds(root_dir): def render_dfhack_keybind(command) -> List[nodes.paragraph]: + out = [] if command not in _KEYBINDS: - return [] - newnode = nodes.paragraph() + return out for keycmd, key, ctx in _KEYBINDS[command]: n = nodes.paragraph() - newnode += n n += nodes.strong('Keybinding:', 'Keybinding:') n += nodes.inline(' ', ' ') for k in key: @@ -61,7 +60,8 @@ def render_dfhack_keybind(command) -> List[nodes.paragraph]: if ctx: n += nodes.inline(' in ', ' in ') n += nodes.literal(ctx, ctx) - return [newnode] + out.append(n) + return out # pylint:disable=unused-argument,dangerous-default-value,too-many-arguments From 6b32e008b3bfd9eb73535ae69ea82fbf73cc14d0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 7 Aug 2022 02:16:38 -0400 Subject: [PATCH 472/854] Attempt to port keybinding documentation verification to new extension Likely requires a sphinx Domain to work with parallel builds properly --- conf.py | 15 --------------- docs/sphinx_extensions/dfhack/tool_docs.py | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/conf.py b/conf.py index 775f94b68..ff4cdc59d 100644 --- a/conf.py +++ b/conf.py @@ -126,24 +126,9 @@ def write_tool_docs(): outfile.write(include) -def all_keybinds_documented(): - """Check that all keybindings are documented with the :dfhack-keybind: - directive somewhere.""" - undocumented_binds = set(KEYBINDS) - tools = set(i[0] for i in DOC_ALL_DIRS) - for t in tools: - with open(('./docs/tools/{}.rst').format(t)) as f: - tool_binds = set(re.findall(':dfhack-keybind:`(.*?)`', f.read())) - undocumented_binds -= tool_binds - if undocumented_binds: - raise ValueError('The following DFHack commands have undocumented ' - 'keybindings: {}'.format(sorted(undocumented_binds))) - - # Actually call the docs generator and run test write_tool_docs() generate_tag_indices() -#all_keybinds_documented() # comment out while we're transitioning # -- General configuration ------------------------------------------------ diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 920bef98e..8c843dce0 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -3,6 +3,7 @@ # https://www.sphinx-doc.org/en/master/development/tutorials/recipe.html # https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rst-directives +import logging import os from typing import List @@ -15,7 +16,10 @@ import sphinx.directives import dfhack.util +logger = sphinx.util.logging.getLogger(__name__) + _KEYBINDS = {} +_KEYBINDS_RENDERED = set() # commands whose keybindings have been rendered def scan_keybinds(root, files, keybindings): """Add keybindings in the specified files to the @@ -45,6 +49,7 @@ def scan_all_keybinds(root_dir): def render_dfhack_keybind(command) -> List[nodes.paragraph]: + _KEYBINDS_RENDERED.add(command) out = [] if command not in _KEYBINDS: return out @@ -64,6 +69,13 @@ def render_dfhack_keybind(command) -> List[nodes.paragraph]: return out +def check_missing_keybinds(): + # FIXME: _KEYBINDS_RENDERED is empty in the parent process under parallel builds + # consider moving to a sphinx Domain to solve this properly + for missing_command in sorted(set(_KEYBINDS.keys()) - _KEYBINDS_RENDERED): + logger.warning('Undocumented keybindings for command: %s', missing_command) + + # pylint:disable=unused-argument,dangerous-default-value,too-many-arguments def dfhack_keybind_role(role, rawtext, text, lineno, inliner, options={}, content=[]): @@ -153,6 +165,9 @@ def register(app): def setup(app): app.connect('builder-inited', register) + # TODO: re-enable once detection is corrected + # app.connect('build-finished', lambda *_: check_missing_keybinds()) + return { 'version': '0.1', 'parallel_read_safe': True, From 1e7ce2602e492e8cbe8b466686f2403a24bf6f1f Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 7 Aug 2022 18:18:51 -0400 Subject: [PATCH 473/854] Shrink tool/command boxes somewhat --- docs/styles/dfhack.css | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/styles/dfhack.css b/docs/styles/dfhack.css index e24ce1c67..4102e6412 100644 --- a/docs/styles/dfhack.css +++ b/docs/styles/dfhack.css @@ -61,7 +61,13 @@ span.pre { overflow-wrap: break-word; } -.dfhack-tool-summary p { +div.dfhack-tool-summary { + margin: 10px 0; + padding: 10px 15px; +} + +div.dfhack-tool-summary p { margin-top: 0; margin-bottom: 0.5em; + line-height: 1em; } From 6e29ddf2d312fb157105d340ff37aade0cd1fbac Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 8 Aug 2022 02:19:07 -0400 Subject: [PATCH 474/854] Move space out of node for better text rendering --- docs/sphinx_extensions/dfhack/tool_docs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 8c843dce0..04318816b 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -97,7 +97,8 @@ class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): @staticmethod def make_labeled_paragraph(label, content, label_class=nodes.strong, content_class=nodes.inline) -> nodes.paragraph: return nodes.paragraph('', '', *[ - label_class('', '{}: '.format(label)), + label_class('', '{}:'.format(label)), + nodes.inline(text=' '), content_class('', content), ]) @@ -119,7 +120,7 @@ class DFHackToolDirective(DFHackToolDirectiveBase): } def render_content(self) -> List[nodes.Node]: - tag_nodes = [nodes.strong(text='Tags: ')] + tag_nodes = [nodes.strong(text='Tags:'), nodes.inline(text=' ')] for tag in self.options.get('tags', []): tag_nodes += [ addnodes.pending_xref(tag, nodes.inline(text=tag), **{ From daf3bc516be17e6fc953cad9e8274fc7504568d6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 8 Aug 2022 02:29:22 -0400 Subject: [PATCH 475/854] Switch to to fix line breaks in text output No visible change in HTML output; PDF looks different but still acceptable --- docs/sphinx_extensions/dfhack/tool_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 04318816b..42d995ff2 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -104,7 +104,7 @@ class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): @staticmethod def wrap_box(*children: List[nodes.Node]) -> nodes.Admonition: - return nodes.admonition('', *children, classes=['dfhack-tool-summary']) + return nodes.topic('', *children, classes=['dfhack-tool-summary']) def render_content(self) -> List[nodes.Node]: raise NotImplementedError From e6b5d5b0c1904f0a8f384aff70117a80f2f3f721 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 8 Aug 2022 17:35:58 -0400 Subject: [PATCH 476/854] Remove commas from tag lists --- docs/plugins/3dveins.rst | 2 +- docs/plugins/autodump.rst | 2 +- docs/plugins/autogems.rst | 2 +- docs/plugins/automelt.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index b14f6145d..63f6cfd8d 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -2,7 +2,7 @@ ======= .. dfhack-tool:: - :tags: fort, mod, map + :tags: fort mod map :index:`Rewrite layer veins to expand in 3D space. <3dveins; Rewrite layer veins to expand in 3D space.>` Existing, flat veins diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst index dc60946b1..3e8d291a0 100644 --- a/docs/plugins/autodump.rst +++ b/docs/plugins/autodump.rst @@ -2,7 +2,7 @@ autodump ======== .. dfhack-tool:: - :tags: fort, auto, fps, items, stockpiles + :tags: fort auto fps items stockpiles .. dfhack-command:: autodump-destroy-here diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index 3ceed0bec..11575aa7a 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -2,7 +2,7 @@ autogems ======== .. dfhack-tool:: autogems - :tags: fort, auto, jobs + :tags: fort auto jobs :no-command: .. dfhack-command:: autogems-reload diff --git a/docs/plugins/automelt.rst b/docs/plugins/automelt.rst index 0ca9f82e9..6c42cbb7e 100644 --- a/docs/plugins/automelt.rst +++ b/docs/plugins/automelt.rst @@ -2,7 +2,7 @@ automelt ======== .. dfhack-tool:: - :tags: fort, auto, items, stockpiles + :tags: fort auto items stockpiles :no-command: :index:`Quickly designate items to be melted. From 2d60c543fd867f787e891061531ca7fd4b289572 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 8 Aug 2022 21:22:55 -0400 Subject: [PATCH 477/854] Remove "Tool:" line --- docs/sphinx_extensions/dfhack/tool_docs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 42d995ff2..ff4bfc9b1 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -135,7 +135,6 @@ class DFHackToolDirective(DFHackToolDirectiveBase): tag_nodes.pop() return [ - self.make_labeled_paragraph('Tool', self.get_name_or_docname()), nodes.paragraph('', '', *tag_nodes), ] From f9930b313aec99beac4257426f0ee969fccca681 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 9 Aug 2022 22:37:24 -0700 Subject: [PATCH 478/854] migrate all docs to the new directives add a summary option for tools and commands so we can output them with their tags and keybindings at the top of the file. --- docs/builtins/alias.rst | 16 ++--- docs/builtins/cls.rst | 15 +++-- docs/builtins/devel/dump-rpc.rst | 9 +-- docs/builtins/die.rst | 13 ++-- docs/builtins/disable.rst | 10 +-- docs/builtins/enable.rst | 16 ++--- docs/builtins/fpause.rst | 9 +-- docs/builtins/help.rst | 10 +-- docs/builtins/hide.rst | 12 ++-- docs/builtins/keybinding.rst | 13 ++-- docs/builtins/kill-lua.rst | 10 +-- docs/builtins/load.rst | 10 +-- docs/builtins/ls.rst | 7 +- docs/builtins/plug.rst | 17 +++-- docs/builtins/reload.rst | 11 ++-- docs/builtins/sc-script.rst | 12 ++-- docs/builtins/script.rst | 12 ++-- docs/builtins/show.rst | 14 ++-- docs/builtins/tags.rst | 11 ++-- docs/builtins/type.rst | 13 ++-- docs/builtins/unload.rst | 7 +- docs/plugins/3dveins.rst | 9 ++- docs/plugins/RemoteFortressReader.rst | 19 ++++-- docs/plugins/add-spatter.rst | 19 +++--- docs/plugins/autobutcher.rst | 13 ++-- docs/plugins/autochop.rst | 10 +-- docs/plugins/autoclothing.rst | 11 ++-- docs/plugins/autodump.rst | 20 +++--- docs/plugins/autofarm.rst | 16 ++--- docs/plugins/autogems.rst | 15 ++--- docs/plugins/autohauler.rst | 17 ++--- docs/plugins/autolabor.rst | 12 ++-- docs/plugins/automaterial.rst | 13 ++-- docs/plugins/automelt.rst | 9 ++- docs/plugins/autonestbox.rst | 25 +++++--- docs/plugins/autotrade.rst | 14 ++-- docs/plugins/blueprint.rst | 13 ++-- docs/plugins/building-hacks.rst | 6 +- docs/plugins/buildingplan.rst | 10 +-- docs/plugins/burrows.rst | 21 +++--- docs/plugins/changeitem.rst | 20 +++--- docs/plugins/changelayer.rst | 15 +++-- docs/plugins/changevein.rst | 14 ++-- docs/plugins/cleanconst.rst | 17 ++--- docs/plugins/cleaners.rst | 32 ++++++---- docs/plugins/cleanowned.rst | 17 ++--- docs/plugins/command-prompt.rst | 7 +- docs/plugins/confirm.rst | 12 ++-- docs/plugins/createitem.rst | 13 ++-- docs/plugins/cursecheck.rst | 7 +- docs/plugins/cxxrandom.rst | 6 +- docs/plugins/debug.rst | 14 ++-- docs/plugins/deramp.rst | 10 +-- docs/plugins/dig-now.rst | 16 ++--- docs/plugins/dig.rst | 71 ++++++++++++--------- docs/plugins/digFlood.rst | 19 +++--- docs/plugins/diggingInvaders.rst | 7 +- docs/plugins/dwarfmonitor.rst | 10 +-- docs/plugins/dwarfvet.rst | 7 +- docs/plugins/embark-assistant.rst | 17 ++--- docs/plugins/embark-tools.rst | 7 +- docs/plugins/eventful.rst | 6 +- docs/plugins/fastdwarf.rst | 6 +- docs/plugins/filltraffic.rst | 40 ++++++------ docs/plugins/fix-unit-occupancy.rst | 10 +-- docs/plugins/fixveins.rst | 10 +-- docs/plugins/flows.rst | 10 +-- docs/plugins/follow.rst | 12 ++-- docs/plugins/forceequip.rst | 17 ++--- docs/plugins/generated-creature-renamer.rst | 20 ++++-- docs/plugins/getplants.rst | 10 +-- docs/plugins/hotkeys.rst | 11 ++-- docs/plugins/infiniteSky.rst | 11 ++-- docs/plugins/isoworldremote.rst | 8 ++- docs/plugins/jobutils.rst | 18 ++++-- docs/plugins/labormanager.rst | 14 ++-- docs/plugins/lair.rst | 9 +-- docs/plugins/liquids.rst | 10 ++- docs/plugins/luasocket.rst | 6 +- docs/plugins/manipulator.rst | 9 ++- docs/plugins/map-render.rst | 6 +- docs/plugins/misery.rst | 6 +- docs/plugins/mode.rst | 6 +- docs/plugins/mousequery.rst | 6 +- docs/plugins/nestboxes.rst | 13 ++-- docs/plugins/orders.rst | 6 +- docs/plugins/pathable.rst | 10 +-- docs/plugins/petcapRemover.rst | 21 +++--- docs/plugins/plants.rst | 10 ++- docs/plugins/power-meter.rst | 12 ++-- docs/plugins/probe.rst | 14 ++-- docs/plugins/prospector.rst | 14 ++-- docs/plugins/regrass.rst | 11 ++-- docs/plugins/rename.rst | 8 ++- docs/plugins/rendermax.rst | 10 +-- docs/plugins/resume.rst | 21 +++--- docs/plugins/reveal.rst | 34 ++++++---- docs/plugins/ruby.rst | 15 +++-- docs/plugins/search.rst | 14 ++-- docs/plugins/seedwatch.rst | 6 +- docs/plugins/showmood.rst | 6 +- docs/plugins/siege-engine.rst | 11 ++-- docs/plugins/sort.rst | 14 ++-- docs/plugins/spectate.rst | 6 +- docs/plugins/steam-engine.rst | 11 ++-- docs/plugins/stockflow.rst | 12 ++-- docs/plugins/stockpiles.rst | 26 +++++--- docs/plugins/stocks.rst | 9 +-- docs/plugins/stonesense.rst | 10 +-- docs/plugins/strangemood.rst | 6 +- docs/plugins/tailor.rst | 17 ++--- docs/plugins/tiletypes.rst | 26 +++++--- docs/plugins/title-folder.rst | 6 +- docs/plugins/title-version.rst | 6 +- docs/plugins/trackstop.rst | 14 ++-- docs/plugins/tubefill.rst | 8 ++- docs/plugins/tweak.rst | 6 +- docs/plugins/workNow.rst | 12 ++-- docs/plugins/workflow.rst | 12 ++-- docs/plugins/xlsxreader.rst | 6 +- docs/plugins/zone.rst | 6 +- docs/sphinx_extensions/dfhack/tool_docs.py | 11 +++- 122 files changed, 913 insertions(+), 665 deletions(-) diff --git a/docs/builtins/alias.rst b/docs/builtins/alias.rst index 6efaf0776..84a761556 100644 --- a/docs/builtins/alias.rst +++ b/docs/builtins/alias.rst @@ -1,13 +1,13 @@ alias ===== -**Tags:** `tag/system` -:dfhack-keybind:`alias` - -:index:`Configure helper aliases for other DFHack commands. -` Aliases are -resolved immediately after built-in commands, which means that an alias cannot -override a built-in command, but can override a command implemented by a plugin -or script. + +.. dfhack-tool:: + :summary: Configure helper aliases for other DFHack commands. + :tags: system + +Aliases are resolved immediately after built-in commands, which means that an +alias cannot override a built-in command, but can override a command implemented +by a plugin or script. Usage: diff --git a/docs/builtins/cls.rst b/docs/builtins/cls.rst index d6a245081..2aad049b4 100644 --- a/docs/builtins/cls.rst +++ b/docs/builtins/cls.rst @@ -1,8 +1,13 @@ cls === -**Tags:** `tag/system` -:dfhack-keybind:`cls` -:index:`Clear the terminal screen. ` Can also -be invoked as ``clear``. Note that this command does not delete command history. -It just clears the text on the screen. +.. dfhack-tool:: + :summary: Clear the terminal screen. + :tags: system + +Can also be invoked as ``clear``. Note that this command does not delete command +history. It just clears the text on the screen. + +Usage:: + + cls diff --git a/docs/builtins/devel/dump-rpc.rst b/docs/builtins/devel/dump-rpc.rst index 65d4dae57..977b6e33c 100644 --- a/docs/builtins/devel/dump-rpc.rst +++ b/docs/builtins/devel/dump-rpc.rst @@ -1,10 +1,11 @@ devel/dump-rpc ============== -Tags: `tag/system` -:dfhack-keybind:`devel/dump-rpc` -:index:`Write RPC endpoint information to the specified file. -` +.. dfhack-tool:: + :summary: Dump RPC endpoint info. + :tags: dev + +Write RPC endpoint information to the specified file. Usage:: diff --git a/docs/builtins/die.rst b/docs/builtins/die.rst index db487da0e..b4873abc5 100644 --- a/docs/builtins/die.rst +++ b/docs/builtins/die.rst @@ -1,7 +1,12 @@ die === -**Tags:** `tag/system` -:dfhack-keybind:`die` -:index:`Instantly exit DF without saving. -` +.. dfhack-tool:: + :summary: Instantly exit DF without saving. + :tags: system + +Use to exit DF quickly and safely. + +Usage:: + + die diff --git a/docs/builtins/disable.rst b/docs/builtins/disable.rst index 758b6ff8f..e109e69e1 100644 --- a/docs/builtins/disable.rst +++ b/docs/builtins/disable.rst @@ -1,11 +1,11 @@ disable ======= -**Tags:** `tag/system` -:dfhack-keybind:`disable` -:index:`Deactivate a DFHack tool that has some persistent effect. -` See the -`enable` command for more info. +.. dfhack-tool:: + :summary: Deactivate a DFHack tool that has some persistent effect. + :tags: system + +See the `enable` command for more info. Usage:: diff --git a/docs/builtins/enable.rst b/docs/builtins/enable.rst index 8705bc4ef..63b0bdc5f 100644 --- a/docs/builtins/enable.rst +++ b/docs/builtins/enable.rst @@ -1,14 +1,14 @@ enable ====== -**Tags:** `tag/system` -:dfhack-keybind:`enable` -:index:`Activate a DFHack tool that has some persistent effect. -` Many plugins -and scripts can be in a distinct enabled or disabled state. Some of them -activate and deactivate automatically depending on the contents of the world -raws. Others store their state in world data. However a number of them have to -be enabled globally, and the init file is the right place to do it. +.. dfhack-tool:: + :summary: Activate a DFHack tool that has some persistent effect. + :tags: system + +Many plugins and scripts can be in a distinct enabled or disabled state. Some +of them activate and deactivate automatically depending on the contents of the +world raws. Others store their state in world data. However a number of them +have to be enabled globally, and the init file is the right place to do it. Most such plugins or scripts support the built-in ``enable`` and `disable` commands. Calling them at any time without arguments prints a list of enabled diff --git a/docs/builtins/fpause.rst b/docs/builtins/fpause.rst index beb783e82..2e8f851f7 100644 --- a/docs/builtins/fpause.rst +++ b/docs/builtins/fpause.rst @@ -1,10 +1,11 @@ fpause ====== -**Tags:** `tag/system` -:dfhack-keybind:`fpause` -:index:`Forces DF to pause. ` This is useful when -your FPS drops below 1 and you lose control of the game. +.. dfhack-tool:: + :summary: Forces DF to pause. + :tags: system + +This is useful when your FPS drops below 1 and you lose control of the game. Usage:: diff --git a/docs/builtins/help.rst b/docs/builtins/help.rst index 4195f2eaa..e1e913747 100644 --- a/docs/builtins/help.rst +++ b/docs/builtins/help.rst @@ -1,11 +1,11 @@ help ==== -**Tags:** `tag/system` -:dfhack-keybind:`help` -:index:`Display help about a command or plugin. -` Can also be invoked as ``?`` -or ``man`` (short for "manual"). +.. dfhack-tool:: + :summary: Display help about a command or plugin. + :tags: system + +Can also be invoked as ``?`` or ``man`` (short for "manual"). Usage:: diff --git a/docs/builtins/hide.rst b/docs/builtins/hide.rst index 087d87726..0ec8f36df 100644 --- a/docs/builtins/hide.rst +++ b/docs/builtins/hide.rst @@ -1,12 +1,12 @@ hide ==== -**Tags:** `tag/system` -:dfhack-keybind:`hide` -:index:`Hide the DFHack terminal window. -` You can show it again with the `show` -command, though you'll need to use it from a `keybinding` set beforehand or the -in-game `command-prompt`. +.. dfhack-tool:: + :summary: Hide the DFHack terminal window. + :tags: system + +You can show it again with the `show` command, though you'll need to use it from +a `keybinding` set beforehand or the in-game `command-prompt`. Only available on Windows. diff --git a/docs/builtins/keybinding.rst b/docs/builtins/keybinding.rst index fd857a42b..5ce8145f4 100644 --- a/docs/builtins/keybinding.rst +++ b/docs/builtins/keybinding.rst @@ -1,12 +1,13 @@ keybinding ========== -**Tags:** `tag/system` -:dfhack-keybind:`keybinding` -:index:`Create hotkeys that will run DFHack commands. -` Like any other -command, it can be used at any time from the console, but bindings are not -remembered between runs of the game unless re-created in `dfhack.init`. +.. dfhack-tool:: + :summary: Create hotkeys that will run DFHack commands. + :tags: system + +Like any other command, it can be used at any time from the console, but +bindings are not remembered between runs of the game unless re-created in +`dfhack.init`. Hotkeys can be any combinations of Ctrl/Alt/Shift with A-Z, 0-9, F1-F12, or ``\``` (the key below the ``Esc`` key. diff --git a/docs/builtins/kill-lua.rst b/docs/builtins/kill-lua.rst index 129268fa8..4de0ab7d1 100644 --- a/docs/builtins/kill-lua.rst +++ b/docs/builtins/kill-lua.rst @@ -1,11 +1,11 @@ kill-lua ======== -**Tags:** `tag/system` -:dfhack-keybind:`kill-lua` -:index:`Gracefully stops any currently-running Lua scripts. -` Use this -command to stop a misbehaving script that appears to be stuck. +.. dfhack-tool:: + :summary: Gracefully stop any currently-running Lua scripts. + :tags: system + +Use this command to stop a misbehaving script that appears to be stuck. Usage:: diff --git a/docs/builtins/load.rst b/docs/builtins/load.rst index cd3aed2c0..d253144a8 100644 --- a/docs/builtins/load.rst +++ b/docs/builtins/load.rst @@ -1,11 +1,11 @@ load ==== -**Tags:** `tag/system` -:dfhack-keybind:`load` -:index:`Load and register a plugin library. -` Also see `unload` and `reload` for -related actions. +.. dfhack-tool:: + :summary: Load and register a plugin library. + :tags: system + +Also see `unload` and `reload` for related actions. Usage:: diff --git a/docs/builtins/ls.rst b/docs/builtins/ls.rst index f03e3e533..702c8bae5 100644 --- a/docs/builtins/ls.rst +++ b/docs/builtins/ls.rst @@ -1,9 +1,10 @@ ls == -**Tags:** `tag/system` -:dfhack-keybind:`ls` -:index:`List available DFHack commands. ` +.. dfhack-tool:: + :summary: List available DFHack commands. + :tags: system + In order to group related commands, each command is associated with a list of tags. You can filter the listed commands by a tag or a substring of the command name. Can also be invoked as ``dir``. diff --git a/docs/builtins/plug.rst b/docs/builtins/plug.rst index 76e31c432..e10c154e5 100644 --- a/docs/builtins/plug.rst +++ b/docs/builtins/plug.rst @@ -1,14 +1,13 @@ plug ==== -**Tags:** `tag/system` -:dfhack-keybind:`plug` -:index:`List available plugins and whether they are enabled. -` +.. dfhack-tool:: + :summary: List available plugins and whether they are enabled. + :tags: system -Usage: +Usage:: -``plug`` - Lists available plugins and whether they are enabled. -``plug [ ...]`` - Lists only the named plugins. + plug [ [ ...]] + +If run with parameters, it lists only the named plugins. Otherwise it will list +all available plugins. diff --git a/docs/builtins/reload.rst b/docs/builtins/reload.rst index 8449c713d..1429e1038 100644 --- a/docs/builtins/reload.rst +++ b/docs/builtins/reload.rst @@ -1,11 +1,12 @@ reload ====== -**Tags:** `tag/system` -:dfhack-keybind:`reload` -:index:`Reload a loaded plugin. ` Developers -use this command to reload a plugin that they are actively modifying. Also see -`load` and `unload` for related actions. +.. dfhack-tool:: + :summary: Reload a loaded plugin. + :tags: system + +Developers use this command to reload a plugin that they are actively modifying. +Also see `load` and `unload` for related actions. Usage:: diff --git a/docs/builtins/sc-script.rst b/docs/builtins/sc-script.rst index 17c0efe25..b3255cb9f 100644 --- a/docs/builtins/sc-script.rst +++ b/docs/builtins/sc-script.rst @@ -1,12 +1,12 @@ sc-script ========= -**Tags:** `tag/system` -:dfhack-keybind:`sc-script` -:index:`Run commands when game state changes occur. -` This is similar to -the static `init-files` but is slightly more flexible since it can be set -dynamically. +.. dfhack-tool:: + :summary: Run commands when game state changes occur. + :tags: system + +This is similar to the static `init-files` but is slightly more flexible since +it can be set dynamically. Usage: diff --git a/docs/builtins/script.rst b/docs/builtins/script.rst index e5576ed50..fbcebff6c 100644 --- a/docs/builtins/script.rst +++ b/docs/builtins/script.rst @@ -1,12 +1,12 @@ script ====== -**Tags:** `tag/system` -:dfhack-keybind:`script` -:index:`Execute a batch file of DFHack commands. -` It reads a text file and -runs each line as a DFHack command as if it had been typed in by the user -- -treating the input like `an init file `. +.. dfhack-tool:: + :summary: Execute a batch file of DFHack commands. + :tags: system + +It reads a text file and runs each line as a DFHack command as if it had been +typed in by the user -- treating the input like `an init file `. Some other tools, such as `autobutcher` and `workflow`, export their settings as the commands to create them - which can later be reloaded with ``script``. diff --git a/docs/builtins/show.rst b/docs/builtins/show.rst index fde642726..7002d5aff 100644 --- a/docs/builtins/show.rst +++ b/docs/builtins/show.rst @@ -1,13 +1,13 @@ show ==== -**Tags:** `tag/system` -:dfhack-keybind:`show` -:index:`Unhides the DFHack terminal window. -` Useful if you have hidden the -terminal with `hide` and you want it back. Since the terminal window won't be -available to run this command, you'll need to use it from a `keybinding` set -beforehand or the in-game `command-prompt`. +.. dfhack-tool:: + :summary: Unhides the DFHack terminal window. + :tags: system + +Useful if you have hidden the terminal with `hide` and you want it back. Since +the terminal window won't be available to run this command, you'll need to use +it from a `keybinding` set beforehand or the in-game `command-prompt`. Only available on Windows. diff --git a/docs/builtins/tags.rst b/docs/builtins/tags.rst index 024677cff..93a094149 100644 --- a/docs/builtins/tags.rst +++ b/docs/builtins/tags.rst @@ -1,11 +1,12 @@ tags ==== -**Tags:** `tag/system` -:dfhack-keybind:`tags` -:index:`List the strings that DFHack tools can be tagged with. -` You can find -groups of related tools by passing the tag name to the `ls` command. +.. dfhack-tool:: + :summary: List the strings that DFHack tools can be tagged with. + :tags: system + +You can find groups of related tools by passing the tag name to the `ls` +command. Usage:: diff --git a/docs/builtins/type.rst b/docs/builtins/type.rst index d7cf9588e..3336cae5f 100644 --- a/docs/builtins/type.rst +++ b/docs/builtins/type.rst @@ -1,12 +1,13 @@ type ==== -**Tags:** `tag/system` -:dfhack-keybind:`type` -:index:`Describe how a command is implemented. -` DFHack commands can be provided -by plugins, scripts, or by the core library itself. The ``type`` command can -tell you which is the source of a particular command. +.. dfhack-tool:: + :summary: Describe how a command is implemented. + :tags: system + +DFHack commands can be provided by plugins, scripts, or by the core library +itself. The ``type`` command can tell you which is the source of a particular +command. Usage:: diff --git a/docs/builtins/unload.rst b/docs/builtins/unload.rst index e79d624ec..132732a7f 100644 --- a/docs/builtins/unload.rst +++ b/docs/builtins/unload.rst @@ -1,9 +1,10 @@ unload ====== -**Tags:** `tag/system` -:dfhack-keybind:`unload` -:index:`Unload a plugin from memory. ` +.. dfhack-tool:: + :summary: Unload a plugin from memory. + :tags: system + Also see `load` and `reload` for related actions. Usage:: diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 63f6cfd8d..c60582310 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -2,13 +2,12 @@ ======= .. dfhack-tool:: + :summary: Rewrite layer veins to expand in 3D space. :tags: fort mod map -:index:`Rewrite layer veins to expand in 3D space. -<3dveins; Rewrite layer veins to expand in 3D space.>` Existing, flat veins -are removed and new 3D veins that naturally span z-levels are generated in -their place. The transformation preserves the mineral counts reported by -`prospect`. +Existing, flat veins are removed and new 3D veins that naturally span z-levels +are generated in their place. The transformation preserves the mineral counts +reported by `prospect`. Usage:: diff --git a/docs/plugins/RemoteFortressReader.rst b/docs/plugins/RemoteFortressReader.rst index da8c3570e..f849266c2 100644 --- a/docs/plugins/RemoteFortressReader.rst +++ b/docs/plugins/RemoteFortressReader.rst @@ -1,12 +1,19 @@ RemoteFortressReader ==================== -**Tags:** `tag/dev` -:dfhack-keybind:`RemoteFortressReader_version` -:dfhack-keybind:`load-art-image-chunk` -:index:`Backend for Armok Vision. -` Provides an API for realtime -remote fortress visualization. See :forums:`Armok Vision <146473>`. +.. dfhack-tool:: + :summary: Backend for Armok Vision. + :tags: dev + :no-command: + +.. dfhack-command:: RemoteFortressReader_version + :summary: Print the loaded RemoteFortressReader version. + +.. dfhack-command:: load-art-image-chunk + :summary: Gets an art image chunk by index. + +This plugin provides an API for realtime remote fortress visualization. See +:forums:`Armok Vision <146473>`. Usage: diff --git a/docs/plugins/add-spatter.rst b/docs/plugins/add-spatter.rst index ed3675132..06739291e 100644 --- a/docs/plugins/add-spatter.rst +++ b/docs/plugins/add-spatter.rst @@ -1,11 +1,14 @@ add-spatter =========== -**Tags:** `tag/adventure`, `tag/fort`, `tag/mod`, `tag/items` -:index:`Make tagged reactions produce contaminants. -` Give some use to all -those poisons that can be bought from caravans! The plugin automatically enables -itself when you load a world with reactions that include names starting with -``SPATTER_ADD_``. These reactions will then produce contaminants on items -instead of improvements. The contaminants are immune to being washed away by -water or destroyed by `clean`. +.. dfhack-tool:: + :summary: Make tagged reactions produce contaminants. + :tags: adventure fort mod items + :no-command: + +Give some use to all those poisons that can be bought from caravans! The plugin +automatically enables itself when you load a world with reactions that include +names starting with ``SPATTER_ADD_``, so there are no commands to run to use it. +These reactions will then produce contaminants on items instead of improvements. +The contaminants are immune to being washed away by water or destroyed by +`clean`. diff --git a/docs/plugins/autobutcher.rst b/docs/plugins/autobutcher.rst index 7a960012e..9379ae9c1 100644 --- a/docs/plugins/autobutcher.rst +++ b/docs/plugins/autobutcher.rst @@ -1,12 +1,13 @@ autobutcher =========== -**Tags:** `tag/fort`, `tag/auto`, `tag/fps`, `tag/animals` -:dfhack-keybind:`autobutcher` -Automatically butcher excess livestock. This plugin monitors how many pets you -have of each gender and age and assigns excess lifestock for slaughter. Requires -that you add the target race(s) to a watch list. Units will be ignored if they -are: +.. dfhack-tool:: + :summary: Automatically butcher excess livestock. + :tags: fort auto fps animals + +This plugin monitors how many pets you have of each gender and age and assigns +excess lifestock for slaughter. It requires that you add the target race(s) to a +watch list. Units will be ignored if they are: * Untamed * Nicknamed (for custom protection; you can use the `rename` ``unit`` tool diff --git a/docs/plugins/autochop.rst b/docs/plugins/autochop.rst index 6b0b7125e..b445384fb 100644 --- a/docs/plugins/autochop.rst +++ b/docs/plugins/autochop.rst @@ -1,10 +1,12 @@ autochop ======== -**Tags:** `tag/fort`, `tag/auto` -:index:`Auto-harvest trees when low on stockpiled logs. -` This plugin can -designate trees for chopping when your stocks are low on logs. +.. dfhack-tool:: + :summary: Auto-harvest trees when low on stockpiled logs. + :tags: fort auto + :no-command: + +This plugin can designate trees for chopping when your stocks are low on logs. Usage:: diff --git a/docs/plugins/autoclothing.rst b/docs/plugins/autoclothing.rst index 3feb22520..a5b57dfb1 100644 --- a/docs/plugins/autoclothing.rst +++ b/docs/plugins/autoclothing.rst @@ -1,11 +1,12 @@ autoclothing ============ -**Tags:** `tag/fort`, `tag/auto`, `tag/jobs` -:dfhack-keybind:`autoclothing` -:index:`Automatically manage clothing work orders. -` It allows you to -set how many of each clothing type every citizen should have. +.. dfhack-tool:: + :summary: Automatically manage clothing work orders. + :tags: fort auto jobs + +This command allows you to set how many of each clothing type every citizen +should have. Usage:: diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst index 3e8d291a0..0fd7ee23c 100644 --- a/docs/plugins/autodump.rst +++ b/docs/plugins/autodump.rst @@ -2,15 +2,20 @@ autodump ======== .. dfhack-tool:: + :summary: Automatically set items in a stockpile to be dumped. :tags: fort auto fps items stockpiles + :no-command: + +.. dfhack-command:: autodump + :summary: Teleports items marked for dumping to the cursor position. .. dfhack-command:: autodump-destroy-here + :summary: Destroy items marked for dumping under the cursor. .. dfhack-command:: autodump-destroy-item + :summary: Destroys the selected item. -:index:`Automatically set items in a stockpile to be dumped. -` When -`enabled `, this plugin adds an option to the :kbd:`q` menu for +When `enabled `, this plugin adds an option to the :kbd:`q` menu for stockpiles. When the ``autodump`` option is selected for the stockpile, any items placed in the stockpile will automatically be designated to be dumped. @@ -44,8 +49,7 @@ Options called again with this option before the game is resumed, it cancels pending destroy actions. ``destroy-here`` - :index:`Destroy items marked for dumping under the cursor. - ` + Destroy items marked for dumping under the cursor. ``visible`` Only process items that are not hidden. ``hidden`` @@ -57,10 +61,8 @@ Examples -------- ``autodump`` - :index:`Teleports items marked for dumping to the cursor position. - ` + Teleports items marked for dumping to the cursor position. ``autodump destroy`` Destroys all unforbidden items marked for dumping ``autodump-destroy-item`` - :index:`Destroys the selected item. - ` + Destroys the selected item. diff --git a/docs/plugins/autofarm.rst b/docs/plugins/autofarm.rst index bfe8ad717..44077eb89 100644 --- a/docs/plugins/autofarm.rst +++ b/docs/plugins/autofarm.rst @@ -1,13 +1,13 @@ autofarm ======== -**Tags:** `tag/fort`, `tag/auto`, `tag/buildings` -:dfhack-keybind:`autofarm` - -:index:`Automatically manage farm crop selection. -` This plugin periodically -scans your plant stocks and assigns crops to your farm plots based on which -plant stocks are low (as long as you have the appropriate seeds). The target -threshold for each crop type is configurable. + +.. dfhack-tool:: + :summary: Automatically manage farm crop selection. + :tags: fort auto buildings + +Periodically scan your plant stocks and assign crops to your farm plots based on +which plant stocks are low (as long as you have the appropriate seeds). The +target threshold for each crop type is configurable. Usage: diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index 11575aa7a..f95d74fea 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -1,15 +1,16 @@ autogems ======== -.. dfhack-tool:: autogems +.. dfhack-tool:: + :summary: Automatically cut rough gems. :tags: fort auto jobs :no-command: .. dfhack-command:: autogems-reload + :summary: Reloads the autogems configuration file. -:index:`Automatically cut rough gems. ` -This plugin periodically scans your stocks of rough gems and creates manager -orders for cutting them at a Jeweler's Workshop. +Automatically cut rough gems. This plugin periodically scans your stocks of +rough gems and creates manager orders for cutting them at a Jeweler's Workshop. Usage: @@ -17,10 +18,8 @@ Usage: Enables the plugin and starts autocutting gems according to its configuration. ``autogems-reload`` - :index:`Reloads the autogems configuration file. - ` You might need - to do this if you have manually modified the contents while the game is - running. + Reloads the autogems configuration file. You might need to do this if you + have manually modified the contents while the game is running. Run `gui/autogems` for a configuration UI, or access the ``Auto Cut Gems`` option from the Current Workshop Orders screen (:kbd:`o`-:kbd:`W`). diff --git a/docs/plugins/autohauler.rst b/docs/plugins/autohauler.rst index e0e1b2bac..1a4a96c8f 100644 --- a/docs/plugins/autohauler.rst +++ b/docs/plugins/autohauler.rst @@ -1,13 +1,14 @@ autohauler ========== -**Tags:** `tag/fort`, `tag/auto`, `tag/labors` -:dfhack-keybind:`autohauler` - -:index:`Automatically manage hauling labors. -` Similar to `autolabor`, but -instead of managing all labors, ``autohauler`` only addresses hauling labors, -leaving the assignment of skilled labors entirely up to you. You can use the -in-game `manipulator` UI or an external tool like Dwarf Therapist to do so. + +.. dfhack-tool:: + :summary: Automatically manage hauling labors. + :tags: fort auto labors + +Similar to `autolabor`, but instead of managing all labors, ``autohauler`` only +addresses hauling labors, leaving the assignment of skilled labors entirely up +to you. You can use the in-game `manipulator` UI or an external tool like Dwarf +Therapist to do so. Idle dwarves who are not on active military duty will be assigned the hauling labors; everyone else (including those currently hauling) will have the hauling diff --git a/docs/plugins/autolabor.rst b/docs/plugins/autolabor.rst index 1d02fcfe8..d2fe49e87 100644 --- a/docs/plugins/autolabor.rst +++ b/docs/plugins/autolabor.rst @@ -1,12 +1,12 @@ autolabor ========= -**Tags:** `tag/fort`, `tag/auto`, `tag/labors` -:dfhack-keybind:`autolabor` -:index:`Automatically manage dwarf labors. -` Autolabor attempts to keep as -many dwarves as possible busy while allowing dwarves to specialize in specific -skills. +.. dfhack-tool:: + :summary: Automatically manage dwarf labors. + :tags: fort auto labors + +Autolabor attempts to keep as many dwarves as possible busy while allowing +dwarves to specialize in specific skills. Autolabor frequently checks how many jobs of each type are available and sets labors proportionally in order to get them all done quickly. Labors with diff --git a/docs/plugins/automaterial.rst b/docs/plugins/automaterial.rst index 1c237f260..513a963d2 100644 --- a/docs/plugins/automaterial.rst +++ b/docs/plugins/automaterial.rst @@ -1,11 +1,14 @@ automaterial ============ -**Tags:** `tag/fort`, `tag/productivity`, `tag/design`, `tag/buildings`, `tag/map` -:index:`Sorts building materials by recent usage. -` This makes building -constructions (walls, floors, fortifications, etc) much easier by saving you -from having to trawl through long lists of materials each time you place one. +.. dfhack-tool:: + :summary: Sorts building materials by recent usage. + :tags: fort productivity design buildings map + :no-command: + +This plugin makes building constructions (walls, floors, fortifications, etc) +much easier by saving you from having to trawl through long lists of materials +each time you place one. It moves the last used material for a given construction type to the top of the list, if there are any left. So if you build a wall with chalk blocks, the next diff --git a/docs/plugins/automelt.rst b/docs/plugins/automelt.rst index 6c42cbb7e..508fdaeaf 100644 --- a/docs/plugins/automelt.rst +++ b/docs/plugins/automelt.rst @@ -2,14 +2,13 @@ automelt ======== .. dfhack-tool:: + :summary: Quickly designate items to be melted. :tags: fort auto items stockpiles :no-command: -:index:`Quickly designate items to be melted. -` When `enabled `, this -plugin adds an option to the :kbd:`q` menu for stockpiles. When the ``automelt`` -option is selected for the stockpile, any items placed in the stockpile will -automatically be designated to be melted. +When `enabled `, this plugin adds an option to the :kbd:`q` menu for +stockpiles. When the ``automelt`` option is selected for the stockpile, any +items placed in the stockpile will automatically be designated to be melted. Usage:: diff --git a/docs/plugins/autonestbox.rst b/docs/plugins/autonestbox.rst index 2f874c452..b82a1a207 100644 --- a/docs/plugins/autonestbox.rst +++ b/docs/plugins/autonestbox.rst @@ -1,16 +1,21 @@ autonestbox =========== -**Tags:** `tag/fort`, `tag/auto`, `tag/animals` -:dfhack-keybind:`autonestbox` -Auto-assign egg-laying female pets to nestbox zones. Requires that you create -pen/pasture zones above nestboxes. If the pen is bigger than 1x1, the nestbox -must be in the top left corner. Only 1 unit will be assigned per pen, regardless -of the size. The age of the units is currently not checked since most birds grow -up quite fast. Egg layers who are also grazers will be ignored, since confining -them to a 1x1 pasture is not a good idea. Only tame and domesticated own units -are processed since pasturing half-trained wild egg layers could destroy your -neat nestbox zones when they revert to wild. +.. dfhack-tool:: + :summary: Auto-assign egg-laying female pets to nestbox zones. + :tags: fort auto animals + +To use this feature, you must create pen/pasture zones above nestboxes. If the +pen is bigger than 1x1, the nestbox must be in the top left corner. Only 1 unit +will be assigned per pen, regardless of the size. Egg layers who are also +grazers will be ignored, since confining them to a 1x1 pasture is not a good +idea. Only tame and domesticated own units are processed since pasturing +half-trained wild egg layers could destroy your neat nestbox zones when they +revert to wild. + +Note that the age of the units is not checked, so you might get some egg-laying +kids assigned to the nestbox zones. Most birds grow up quite fast, though, so +they should be adults and laying eggs soon enough. Usage: diff --git a/docs/plugins/autotrade.rst b/docs/plugins/autotrade.rst index 35657fc88..f27fab122 100644 --- a/docs/plugins/autotrade.rst +++ b/docs/plugins/autotrade.rst @@ -1,12 +1,14 @@ autotrade ========= -**Tags:** `tag/fort`, `tag/auto`, `tag/items`, `tag/stockpiles` -:index:`Quickly designate items to be traded. -` When `enabled `, -this plugin adds an option to the :kbd:`q` menu for stockpiles. When the -``autotrade`` option is selected for the stockpile, any items placed in the -stockpile will automatically be designated to be traded. +.. dfhack-tool:: + :summary: Quickly designate items to be traded. + :tags: fort auto items stockpiles + :no-command: + +When `enabled `, this plugin adds an option to the :kbd:`q` menu for +stockpiles. When the ``autotrade`` option is selected for the stockpile, any +items placed in the stockpile will automatically be designated to be traded. Usage:: diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst index 48ffdd883..8654aa1ec 100644 --- a/docs/plugins/blueprint.rst +++ b/docs/plugins/blueprint.rst @@ -1,12 +1,13 @@ blueprint ========= -**Tags:** `tag/fort`, `tag/design`, `tag/quickfort`, `tag/map` -:dfhack-keybind:`blueprint` -:index:`Record a live game map in a quickfort blueprint. -` With -``blueprint``, you can export the structure of a portion of your fortress in a -blueprint file that you (or anyone else) can later play back with `quickfort`. +.. dfhack-tool:: + :summary: Record a live game map in a quickfort blueprint. + :tags: fort design quickfort map + +With ``blueprint``, you can export the structure of a portion of your fortress +in a blueprint file that you (or anyone else) can later play back with +`quickfort`. Blueprints are ``.csv`` or ``.xlsx`` files created in the ``blueprints`` subdirectory of your DF folder. The map area to turn into a blueprint is either diff --git a/docs/plugins/building-hacks.rst b/docs/plugins/building-hacks.rst index 1f8ee1b2b..cd8835f37 100644 --- a/docs/plugins/building-hacks.rst +++ b/docs/plugins/building-hacks.rst @@ -1,7 +1,9 @@ building-hacks ============== -**Tags:** `tag/fort`, `tag/mod`, `tag/buildings` -Provides a Lua API for creating powered workshops. +.. dfhack-tool:: + :summary: Provides a Lua API for creating powered workshops. + :tags: fort mod buildings + :no-command: See `building-hacks-api` for more details. diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index be95dc6f1..ea9d78f64 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -1,11 +1,11 @@ buildingplan ============ -**Tags:** `tag/fort`, `tag/design`, `tag/quickfort`, `tag/buildings`, `tag/map` -:dfhack-keybind:`buildingplan` -:index:`Plan building construction before you have materials. -` This -plugin adds a planning mode for building placement. You can then place +.. dfhack-tool:: + :summary: Plan building construction before you have materials. + :tags: fort design quickfort buildings map + +This plugin adds a planning mode for building placement. You can then place furniture, constructions, and other buildings before the required materials are available, and they will be created in a suspended state. Buildingplan will periodically scan for appropriate items, and the jobs will be unsuspended when diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst index 4728e2cd5..92e62f08b 100644 --- a/docs/plugins/burrows.rst +++ b/docs/plugins/burrows.rst @@ -1,24 +1,23 @@ burrows ======= -**Tags:** `tag/fort`, `tag/auto`, `tag/productivity`, `tag/units` -:dfhack-keybind:`burrow` -:index:`Auto-expand burrows as you dig. -` When a wall inside a burrow -with a name ending in ``+`` is dug out, the burrow will be extended to -newly-revealed adjacent walls. Note that digging 1-wide corridors with the miner -inside the burrow is **SLOW**. +.. dfhack-tool:: + :summary: Auto-expand burrows as you dig. + :tags: fort auto productivity units + :no-command: -You can also use the ``burrow`` command to -:index:`quickly add units/tiles to burrows. -` +.. dfhack-command:: burrow + :summary: Quickly add units/tiles to burrows. + +When a wall inside a burrow with a name ending in ``+`` is dug out, the burrow +will be extended to newly-revealed adjacent walls. Usage: ``burrow enable auto-grow`` When a wall inside a burrow with a name ending in '+' is dug out, the burrow will be extended to newly-revealed adjacent walls. This final '+' may be - omitted in burrow name args of other ``burrows`` commands. Note that digging + omitted in burrow name args of other ``burrow`` commands. Note that digging 1-wide corridors with the miner inside the burrow is SLOW. ``burrow disable auto-grow`` Disables auto-grow processing. diff --git a/docs/plugins/changeitem.rst b/docs/plugins/changeitem.rst index 68c53f821..e49698822 100644 --- a/docs/plugins/changeitem.rst +++ b/docs/plugins/changeitem.rst @@ -1,15 +1,15 @@ changeitem ========== -**Tags:** `tag/adventure`, `tag/fort`, `tag/armok`, `tag/items` -:dfhack-keybind:`changeitem` - -:index:`Change item material and base quality. -` By default, a change is -only allowed if the existing and desired item materials are of the same subtype -(for example wood -> wood, stone -> stone, etc). But since some transformations -work pretty well and may be desired you can override this with ``force``. Note -that forced changes can possibly result in items that crafters and haulers -refuse to touch. + +.. dfhack-tool:: + :summary: Change item material or base quality. + :tags: adventure fort armok items + +By default, a change is only allowed if the existing and desired item materials +are of the same subtype (for example wood -> wood, stone -> stone, etc). But +since some transformations work pretty well and may be desired you can override +this with ``force``. Note that forced changes can possibly result in items that +crafters and haulers refuse to touch. Usage: diff --git a/docs/plugins/changelayer.rst b/docs/plugins/changelayer.rst index 1267fbdac..e44d6f317 100644 --- a/docs/plugins/changelayer.rst +++ b/docs/plugins/changelayer.rst @@ -1,13 +1,14 @@ changelayer =========== -**Tags:** `tag/fort`, `tag/armok`, `tag/map` -:dfhack-keybind:`changelayer` -:index:`Change the material of an entire geology layer. -` Note that one -layer can stretch across many z-levels, and changes to the geology layer will -affect all surrounding regions, not just your embark! Mineral veins and gem -clusters will not be affected. Use `changevein` if you want to modify those. +.. dfhack-tool:: + :summary: Change the material of an entire geology layer. + :tags: fort armok map + +Note that one layer can stretch across many z-levels, and changes to the geology +layer will affect all surrounding regions, not just your embark! Mineral veins +and gem clusters will not be affected. Use `changevein` if you want to modify +those. tl;dr: You will end up with changing large areas in one go, especially if you use it in lower z levels. Use this command with care! diff --git a/docs/plugins/changevein.rst b/docs/plugins/changevein.rst index 96c68de76..4811c416c 100644 --- a/docs/plugins/changevein.rst +++ b/docs/plugins/changevein.rst @@ -1,13 +1,13 @@ changevein ========== -**Tags:** `tag/fort`, `tag/armok`, `tag/map` -:dfhack-keybind:`changevein` -:index:`Change the material of a mineral inclusion. -` You can change it to -any incorganic material RAW id. Note that this command only affects tiles within -the current 16x16 block - for large veins and clusters, you will need to use -this command multiple times. +.. dfhack-tool:: + :summary: Change the material of a mineral inclusion. + :tags: fort armok map + +You can change a vein to any incorganic material RAW id. Note that this command +only affects tiles within the current 16x16 block - for large veins and +clusters, you will need to use this command multiple times. You can use the `probe` command to discover the material RAW ids for existing veins that you want to duplicate. diff --git a/docs/plugins/cleanconst.rst b/docs/plugins/cleanconst.rst index c47a8c6dd..9c0309105 100644 --- a/docs/plugins/cleanconst.rst +++ b/docs/plugins/cleanconst.rst @@ -1,13 +1,14 @@ cleanconst ========== -**Tags:** `tag/fort`, `tag/fps`, `tag/map` -:dfhack-keybind:`cleanconst` - -:index:`Cleans up construction materials. -` This tool alters all -constructions on the map so that they spawn their building component when they -are disassembled, allowing their actual build items to be safely deleted. This -can improve FPS when you have many constructions on the map. + +.. dfhack-tool:: + :summary: Cleans up construction materials. + :tags: fort fps map + +This tool alters all constructions on the map so that they spawn their building +component when they are disassembled, allowing their actual build items to be +safely deleted. This can improve FPS when you have many constructions on the +map. Usage:: diff --git a/docs/plugins/cleaners.rst b/docs/plugins/cleaners.rst index 20fa792db..42e585da1 100644 --- a/docs/plugins/cleaners.rst +++ b/docs/plugins/cleaners.rst @@ -3,15 +3,22 @@ cleaners ======== -**Tags:** `tag/adventure`, `tag/fort`, `tag/fps`, `tag/items`, `tag/map`, `tag/units` -:dfhack-keybind:`clean` -:dfhack-keybind:`spotclean` -:index:`Removes contaminants. ` More -specifically, it cleans all the splatter that get scattered all over the map and -that clings to your items and units. In an old fortress, cleaning with this tool -can significantly reduce FPS lag. It can also spoil your !!FUN!!, so think -before you use it. +.. dfhack-tool:: + :summary: Provides commands for cleaning spatter from the map. + :tags: adventure fort fps items map units + :no-command: + +.. dfhack-command:: clean + :summary: Removes contaminants. + +.. dfhack-command:: spotclean + :summary: Remove all contaminants from the tile under the cursor. + +This plugin provides commands that clean the splatter that get scattered all +over the map and that clings to your items and units. In an old fortress, +cleaning with this tool can significantly reduce FPS lag! It can also spoil your +!!FUN!!, so think before you use it. Usage:: @@ -21,11 +28,10 @@ Usage:: By default, cleaning the map leaves mud and snow alone. Note that cleaning units includes hostiles, and that cleaning items removes poisons from weapons. -``spotclean`` works like ``clean map snow mud``, -:index:`removing all contaminants from the tile under the cursor. -` This is ideal -if you just want to clean a specific tile but don't want the `clean` command to -remove all the glorious blood from your entranceway. +``spotclean`` works like ``clean map snow mud``, removing all contaminants from +the tile under the cursor. This is ideal if you just want to clean a specific +tile but don't want the `clean` command to remove all the glorious blood from +your entranceway. Examples -------- diff --git a/docs/plugins/cleanowned.rst b/docs/plugins/cleanowned.rst index 409c39345..7271ce402 100644 --- a/docs/plugins/cleanowned.rst +++ b/docs/plugins/cleanowned.rst @@ -1,13 +1,14 @@ cleanowned ========== -**Tags:** `tag/fort`, `tag/productivity`, `tag/items` -:dfhack-keybind:`cleanowned` - -:index:`Confiscates and dumps garbage owned by dwarves. -` This tool gets -dwarves to give up ownership of scattered items and items with heavy wear and -then marks those items for dumping. Now you can finally get your dwarves to give -up their rotten food and tattered loincloths and go get new ones! + +.. dfhack-tool:: + :summary: Confiscates and dumps garbage owned by dwarves. + :tags: fort productivity items + +This tool gets dwarves to give up ownership of scattered items and items with +heavy wear and then marks those items for dumping. Now you can finally get your +dwarves to give up their rotten food and tattered loincloths and go get new +ones! Usage:: diff --git a/docs/plugins/command-prompt.rst b/docs/plugins/command-prompt.rst index 312734b97..278741f36 100644 --- a/docs/plugins/command-prompt.rst +++ b/docs/plugins/command-prompt.rst @@ -1,10 +1,9 @@ command-prompt ============== -**Tags:** `tag/system` -:dfhack-keybind:`command-prompt` -:index:`An in-game DFHack terminal where you can enter other commands. -` +.. dfhack-tool:: + :summary: An in-game DFHack terminal where you can run other commands. + :tags: system Usage:: diff --git a/docs/plugins/confirm.rst b/docs/plugins/confirm.rst index de2556b6f..aef18c9c0 100644 --- a/docs/plugins/confirm.rst +++ b/docs/plugins/confirm.rst @@ -1,12 +1,12 @@ confirm ======= -**Tags:** `tag/fort`, `tag/interface` -:dfhack-keybind:`confirm` -:index:`Adds confirmation dialogs for destructive actions. -` Now you can get -the chance to avoid seizing goods from traders or deleting a hauling route in -case you hit the key accidentally. +.. dfhack-tool:: + :summary: Adds confirmation dialogs for destructive actions. + :tags: fort interface + +Now you can get the chance to avoid seizing goods from traders or deleting a +hauling route in case you hit the key accidentally. Usage: diff --git a/docs/plugins/createitem.rst b/docs/plugins/createitem.rst index 38f072e62..dfd286395 100644 --- a/docs/plugins/createitem.rst +++ b/docs/plugins/createitem.rst @@ -1,12 +1,13 @@ createitem ========== -**Tags:** `tag/adventure`, `tag/fort`, `tag/armok`, `tag/items` -:dfhack-keybind:`createitem` -:index:`Create arbitrary items. ` You can -create new items of any type and made of any material. A unit must be selected -in-game to use this command. By default, items created are spawned at the feet -of the selected unit. +.. dfhack-tool:: + :summary: Create arbitrary items. + :tags: adventure fort armok items + +You can create new items of any type and made of any material. A unit must be +selected in-game to use this command. By default, items created are spawned at +the feet of the selected unit. Specify the item and material information as you would indicate them in custom reaction raws, with the following differences: diff --git a/docs/plugins/cursecheck.rst b/docs/plugins/cursecheck.rst index ecb4dbd2f..b2e2943ba 100644 --- a/docs/plugins/cursecheck.rst +++ b/docs/plugins/cursecheck.rst @@ -1,9 +1,10 @@ cursecheck ========== -**Tags:** `tag/system`, `tag/interface` -:dfhack-keybind:`cursecheck` -:index:`Check for cursed creatures. ` +.. dfhack-tool:: + :summary: Check for cursed creatures. + :tags: system interface + This command checks a single map tile (or the whole map/world) for cursed creatures (ghosts, vampires, necromancers, werebeasts, zombies, etc.). diff --git a/docs/plugins/cxxrandom.rst b/docs/plugins/cxxrandom.rst index 0016fb8d0..19788e4a4 100644 --- a/docs/plugins/cxxrandom.rst +++ b/docs/plugins/cxxrandom.rst @@ -1,7 +1,9 @@ cxxrandom ========= -**Tags:** `tag/dev` -Provides a Lua API for random distributions. +.. dfhack-tool:: + :summary: Provides a Lua API for random distributions. + :tags: dev + :no-command: See `cxxrandom-api` for details. diff --git a/docs/plugins/debug.rst b/docs/plugins/debug.rst index 4cc94b217..a9e601c77 100644 --- a/docs/plugins/debug.rst +++ b/docs/plugins/debug.rst @@ -1,11 +1,15 @@ debug ===== -**Tags:** `tag/dev` -:dfhack-keybind:`debugfilter` -:index:`Configure verbosity of DFHack debug output. -` Debug output is -grouped by plugin name, category name, and verbosity level. +.. dfhack-tool:: + :summary: Provides commands for controlling debug log verbosity. + :tags: dev + :no-command: + +.. dfhack-command:: debugfilter + :summary: Configure verbosity of DFHack debug output. + +Debug output is grouped by plugin name, category name, and verbosity level. The verbosity levels are: diff --git a/docs/plugins/deramp.rst b/docs/plugins/deramp.rst index 6126d2828..4bb96ef0a 100644 --- a/docs/plugins/deramp.rst +++ b/docs/plugins/deramp.rst @@ -1,11 +1,11 @@ deramp ====== -**Tags:** `tag/fort`, `tag/armok`, `tag/map` -:dfhack-keybind:`deramp` -:index:`Removes all ramps designated for removal from the map. -` It also -removes any "floating" down ramps that can remain after a cave-in. +.. dfhack-tool:: + :summary: Removes all ramps designated for removal from the map. + :tags: fort armok map + +It also removes any "floating" down ramps that can remain after a cave-in. Usage:: diff --git a/docs/plugins/dig-now.rst b/docs/plugins/dig-now.rst index 7d3beedc0..0562e000a 100644 --- a/docs/plugins/dig-now.rst +++ b/docs/plugins/dig-now.rst @@ -1,14 +1,14 @@ dig-now ======= -**Tags:** `tag/fort`, `tag/armok`, `tag/map` -:dfhack-keybind:`dig-now` -:index:`Instantly complete dig designations. -` This tool will magically -complete non-marker dig designations, modifying tile shapes and creating -boulders, ores, and gems as if a miner were doing the mining or engraving. By -default, the entire map is processed and boulder generation follows standard -game rules, but the behavior is configurable. +.. dfhack-tool:: + :summary: Instantly complete dig designations. + :tags: fort armok map + +This tool will magically complete non-marker dig designations, modifying tile +shapes and creating boulders, ores, and gems as if a miner were doing the mining +or engraving. By default, the entire map is processed and boulder generation +follows standard game rules, but the behavior is configurable. Note that no units will get mining or engraving experience for the dug/engraved tiles. diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst index f4e0c8acb..1761b7085 100644 --- a/docs/plugins/dig.rst +++ b/docs/plugins/dig.rst @@ -3,47 +3,58 @@ dig === -**Tags:** `tag/fort`, `tag/productivity`, `tag/design`, `tag/map` -:dfhack-keybind:`digv` -:dfhack-keybind:`digvx` -:dfhack-keybind:`digl` -:dfhack-keybind:`diglx` -:dfhack-keybind:`digcircle` -:dfhack-keybind:`digtype` -:dfhack-keybind:`digexp` -Make complicated dig patterns easy. +.. dfhack-tool:: + :summary: Provides commands for designating tiles for digging. + :tags: fort productivity design map + :no-command: + +.. dfhack-command:: digv + :summary: Designate all of the selected vein for digging. + +.. dfhack-command:: digvx + :summary: Dig a vein across z-levels, digging stairs as needed. + +.. dfhack-command:: digl + :summary: Dig all of the selected layer stone. + +.. dfhack-command:: diglx + :summary: Dig layer stone across z-levels, digging stairs as needed. + +.. dfhack-command:: digcircle + :summary: Designate circles. + +.. dfhack-command:: digtype + :summary: Designate all vein tiles of the selected type. + +.. dfhack-command:: digexp + :summary: Designate dig patterns for exploratory mining. + +This plugin provides commands to make complicated dig patterns easy. Usage: ``digv [x] [-p]`` - :index:`Designate all of the selected vein for digging. - ` + Designate all of the selected vein for digging. ``digvx [-p]`` - :index:`Dig a vein across z-levels, digging stairs as needed. - ` - This is an alias for ``digv x``. + Dig a vein across z-levels, digging stairs as needed. This is an alias for + ``digv x``. ``digl [x] [undo] [-p]`` - :index:`Dig all of the selected layer stone. - ` If ``undo`` is specified, - removes the designation instead (for if you accidentally set 50 levels at - once). + Dig all of the selected layer stone. If ``undo`` is specified, removes the + designation instead (for if you accidentally set 50 levels at once). ``diglx [-p]`` - :index:`Dig layer stone across z-levels, digging stairs as needed. - ` This - is an alias for ``digl x``. + Dig layer stone across z-levels, digging stairs as needed. This is an alias + for ``digl x``. ``digcircle [] [] [] [] [-p]`` - :index:`Designate circles. ` The diameter - is the number of tiles across the center of the circle that you want to dig. - See the `digcircle`_ section below for options. + Designate circles. The diameter is the number of tiles across the center of + the circle that you want to dig. See the `digcircle`_ section below for + options. ``digtype [] [-p]`` - :index:`Designate all vein tiles of the selected type. - ` See the `digtype`_ - section below for options. + Designate all vein tiles of the selected type. See the `digtype`_ section + below for options. ``digexp [] [] [-p]`` - :index:`Designate dig patterns for exploratory mining. - ` See the `digexp`_ - section below for options + Designate dig patterns for exploratory mining. See the `digexp`_ section + below for options. All commands support specifying the priority of the dig designations with ``-p``, where the number is from 1 to 7. If a priority is not specified, diff --git a/docs/plugins/digFlood.rst b/docs/plugins/digFlood.rst index 1a17c32a7..60bbc9423 100644 --- a/docs/plugins/digFlood.rst +++ b/docs/plugins/digFlood.rst @@ -1,13 +1,16 @@ digFlood ======== -**Tags:** `tag/fort`, `tag/auto`, `tag/map` -:dfhack-keybind:`digFlood` - -:index:`Digs out veins as they are discovered. -` It will only dig out -appropriate tiles that are adjacent to a just-finished dig job, so if you want -to autodig a vein that has already been discovered, you may need to manually -designate one tile of the tile for digging to get started. + +.. dfhack-tool:: + :summary: Digs out veins as they are discovered. + :tags: fort auto map + +Once you register specific vein types, this tool will automatically designate +tiles of those types of veins for digging as your miners complete adjacent +mining jobs. Note that it will *only* dig out tiles that are adjacent to a +just-finished dig job, so if you want to autodig a vein that has already been +discovered, you may need to manually designate one tile of the tile for digging +to get started. Usage: diff --git a/docs/plugins/diggingInvaders.rst b/docs/plugins/diggingInvaders.rst index 4891bf8d4..df54e7008 100644 --- a/docs/plugins/diggingInvaders.rst +++ b/docs/plugins/diggingInvaders.rst @@ -1,10 +1,9 @@ diggingInvaders =============== -**Tags:** `tag/fort`, `tag/mod`, `tag/map` -:dfhack-keybind:`diggingInvaders` -:index:`Invaders dig and destroy to get to your dwarves. -` +.. dfhack-tool:: + :summary: Invaders dig and destroy to get to your dwarves. + :tags: fort mod map Usage: diff --git a/docs/plugins/dwarfmonitor.rst b/docs/plugins/dwarfmonitor.rst index a7f42bb81..73d8a8c19 100644 --- a/docs/plugins/dwarfmonitor.rst +++ b/docs/plugins/dwarfmonitor.rst @@ -1,11 +1,11 @@ dwarfmonitor ============ -**Tags:** `tag/fort`, `tag/inspection`, `tag/units` -:dfhack-keybind:`dwarfmonitor` -:index:`Measure fort happiness and efficiency. -` Also show heads-up -display widgets with live fort statistics. +.. dfhack-tool:: + :summary: Measure fort happiness and efficiency. + :tags: fort inspection units + +It can also show heads-up display widgets with live fort statistics. Usage: diff --git a/docs/plugins/dwarfvet.rst b/docs/plugins/dwarfvet.rst index 2db4a9595..f607e85cd 100644 --- a/docs/plugins/dwarfvet.rst +++ b/docs/plugins/dwarfvet.rst @@ -1,10 +1,9 @@ dwarfvet ======== -**Tags:** `tag/fort`, `tag/mod`, `tag/animals` -:dfhack-keybind:`dwarfvet` -:index:`Allows animals to be treated at animal hospitals. -` +.. dfhack-tool:: + :summary: Allows animals to be treated at animal hospitals. + :tags: fort mod animals Annoyed your dragons become useless after a minor injury? Well, with dwarfvet, injured animals will be treated at an animal hospital, which is simply a hospital diff --git a/docs/plugins/embark-assistant.rst b/docs/plugins/embark-assistant.rst index b9a7e4ee2..22c18996a 100644 --- a/docs/plugins/embark-assistant.rst +++ b/docs/plugins/embark-assistant.rst @@ -1,14 +1,15 @@ embark-assistant ================ -**Tags:** `tag/fort`, `tag/embark`, `tag/interface` -:dfhack-keybind:`embark-assistant` -:index:`Embark site selection support. -` Run this command while the -pre-embark screen is displayed to show extended (and correct(?)) resource -information for the embark rectangle as well as normally undisplayed sites in -the current embark region. You will also have access to a site selection tool -with far more options than DF's vanilla search tool. +.. dfhack-tool:: + :summary: Embark site selection support. + :tags: fort embark interface + +Run this command while the pre-embark screen is displayed to show extended (and +reasonably correct) resource information for the embark rectangle as well as +normally undisplayed sites in the current embark region. You will also have +access to a site selection tool with far more options than DF's vanilla search +tool. If you enable the plugin, you'll also be able to invoke ``embark-assistant`` with the :kbd:`A` key on the pre-embark screen. diff --git a/docs/plugins/embark-tools.rst b/docs/plugins/embark-tools.rst index 52532e4f2..58b02d47e 100644 --- a/docs/plugins/embark-tools.rst +++ b/docs/plugins/embark-tools.rst @@ -1,10 +1,9 @@ embark-tools ============ -**Tags:** `tag/fort`, `tag/embark`, `tag/interface` -:dfhack-keybind:`embark-tools` -:index:`Extend the embark screen functionality. -` +.. dfhack-tool:: + :summary: Extend the embark screen functionality. + :tags: fort embark interface Usage:: diff --git a/docs/plugins/eventful.rst b/docs/plugins/eventful.rst index 102287b77..d5bd55956 100644 --- a/docs/plugins/eventful.rst +++ b/docs/plugins/eventful.rst @@ -1,7 +1,9 @@ eventful ======== -**Tags:** `tag/dev`, `tag/mod` -Provides a Lua API for reacting to in-game events. +.. dfhack-tool:: + :summary: Provides a Lua API for reacting to in-game events. + :tags: dev mod + :no-command: See `eventful-api` for details. diff --git a/docs/plugins/fastdwarf.rst b/docs/plugins/fastdwarf.rst index 2e09b8a4d..7042a71a3 100644 --- a/docs/plugins/fastdwarf.rst +++ b/docs/plugins/fastdwarf.rst @@ -1,9 +1,9 @@ fastdwarf ========= -**Tags:** `tag/fort`, `tag/armok`, `tag/units` -:dfhack-keybind:`fastdwarf` -Dwarves teleport and/or finish jobs instantly. +.. dfhack-tool:: + :summary: Dwarves teleport and/or finish jobs instantly. + :tags: fort armok units Usage:: diff --git a/docs/plugins/filltraffic.rst b/docs/plugins/filltraffic.rst index acc4dabc8..ea48fd385 100644 --- a/docs/plugins/filltraffic.rst +++ b/docs/plugins/filltraffic.rst @@ -3,24 +3,28 @@ filltraffic =========== -**Tags:** `tag/fort`, `tag/productivity`, `tag/design`, `tag/map` -:dfhack-keybind:`filltraffic` -:dfhack-keybind:`alltraffic` -:dfhack-keybind:`restrictice` -:dfhack-keybind:`restrictliquids` - -Usage: - -``filltraffic []`` - Set traffic designations using flood-fill starting at the cursor. Flood - filling stops at walls and doors. -``alltraffic `` - Set traffic designations for every single tile of the map - useful for - resetting traffic designations. -``restrictice`` - Restrict traffic on all tiles on top of visible ice. -``restrictliquids`` - Restrict traffic on all visible tiles with liquid. + +.. dfhack-tool:: + :summary: Set traffic designations using flood-fill starting at the cursor. + :tags: fort productivity design map + +.. dfhack-command:: alltraffic + :summary: Set traffic designations for every single tile of the map. + +.. dfhack-command:: restrictice + :summary: Restrict traffic on all tiles on top of visible ice. + +.. dfhack-command:: restrictliquids + :summary: Restrict traffic on all visible tiles with liquid. + +Usage:: + + filltraffic [] + alltraffic + restrictice + restrictliquids + +For ``filltraffic``, flood filling stops at walls and doors. Examples -------- diff --git a/docs/plugins/fix-unit-occupancy.rst b/docs/plugins/fix-unit-occupancy.rst index 28479791f..117f97467 100644 --- a/docs/plugins/fix-unit-occupancy.rst +++ b/docs/plugins/fix-unit-occupancy.rst @@ -1,10 +1,12 @@ fix-unit-occupancy ================== -**Tags:** `tag/fort`, `tag/fix`, `tag/map` -:dfhack-keybind:`` -Fix phantom unit occupancy issues. For example, if you see "unit blocking tile" -messages that you can't account for (:bug:`3499`), this tool can help. +.. dfhack-tool:: + :summary: Fix phantom unit occupancy issues. + :tags: fort fix map + +If you see "unit blocking tile" messages that you can't account for +(:bug:`3499`), this tool can help. Usage:: diff --git a/docs/plugins/fixveins.rst b/docs/plugins/fixveins.rst index 873e1770a..045b3c8da 100644 --- a/docs/plugins/fixveins.rst +++ b/docs/plugins/fixveins.rst @@ -1,10 +1,12 @@ fixveins ======== -**Tags:** `tag/fort`, `tag/fix`, `tag/map` -:dfhack-keybind:`fixveins` -Restore missing mineral inclusions. This tool can also remove invalid references -to mineral inclusions if you broke your embark with tools like `tiletypes`. +.. dfhack-tool:: + :summary: Restore missing mineral inclusions. + :tags: fort fix map + +This tool can also remove invalid references to mineral inclusions if you broke +your embark with tools like `tiletypes`. Usage:: diff --git a/docs/plugins/flows.rst b/docs/plugins/flows.rst index a347f00e0..994057878 100644 --- a/docs/plugins/flows.rst +++ b/docs/plugins/flows.rst @@ -1,10 +1,12 @@ flows ===== -**Tags:** `tag/fort`, `tag/inspection`, `tag/map` -:dfhack-keybind:`flows` -Counts map blocks with flowing liquids.. If you suspect that your magma sea -leaks into HFS, you can use this tool to be sure without revealing the map. +.. dfhack-tool:: + :summary: Counts map blocks with flowing liquids. + :tags: fort inspection map + +If you suspect that your magma sea leaks into HFS, you can use this tool to be +sure without revealing the map. Usage:: diff --git a/docs/plugins/follow.rst b/docs/plugins/follow.rst index 5f43e2fdd..b6bc610b2 100644 --- a/docs/plugins/follow.rst +++ b/docs/plugins/follow.rst @@ -1,11 +1,13 @@ follow ====== -**Tags:** `tag/fort`, `tag/interface`, `tag/units` -:dfhack-keybind:`follow` -Make the screen follow the selected unit. Once you exit from the current menu or -cursor mode, the screen will stay centered on the unit. Handy for watching -dwarves running around. Deactivated by moving the cursor manually. +.. dfhack-tool:: + :summary: Make the screen follow the selected unit. + :tags: fort interface units + +Once you exit from the current menu or cursor mode, the screen will stay +centered on the unit. Handy for watching dwarves running around. Deactivated by +moving the cursor manually. Usage:: diff --git a/docs/plugins/forceequip.rst b/docs/plugins/forceequip.rst index 51d48390c..acfd8f462 100644 --- a/docs/plugins/forceequip.rst +++ b/docs/plugins/forceequip.rst @@ -1,14 +1,15 @@ forceequip ========== -**Tags:** `tag/adventure`, `tag/fort`, `tag/items`, `tag/units` -:dfhack-keybind:`forceequip` -Move items into a unit's inventory. This tool is typically used to equip -specific clothing/armor items onto a dwarf, but can also be used to put armor -onto a war animal or to add unusual items (such as crowns) to any unit. Make -sure the unit you want to equip is standing on the target items, which must be -on the ground and be unforbidden. If multiple units are standing on the same -tile, the first one will be equipped. +.. dfhack-tool:: + :summary: Move items into a unit's inventory. + :tags: adventure fort items units + +This tool is typically used to equip specific clothing/armor items onto a dwarf, +but can also be used to put armor onto a war animal or to add unusual items +(such as crowns) to any unit. Make sure the unit you want to equip is standing +on the target items, which must be on the ground and be unforbidden. If multiple +units are standing on the same tile, the first one will be equipped. The most reliable way to set up the environment for this command is to pile target items on a tile of floor with a garbage dump activity zone or the diff --git a/docs/plugins/generated-creature-renamer.rst b/docs/plugins/generated-creature-renamer.rst index 39983940b..6af13595a 100644 --- a/docs/plugins/generated-creature-renamer.rst +++ b/docs/plugins/generated-creature-renamer.rst @@ -1,12 +1,20 @@ generated-creature-renamer ========================== -**Tags:** `tag/adventure`, `tag/fort`, `tag/legends`, `tag/units` -:dfhack-keybind:`list-generated` -:dfhack-keybind:`save-generated-raws` -Automatically renames generated creatures. Now, forgotten beasts, titans, -necromancer experiments, etc. will have raw token names that match the -description given in-game instead of unreadable generated strings. +.. dfhack-tool:: + :summary: Automatically renames generated creatures. + :tags: adventure fort legends units + :no-command: + +.. dfhack-command:: list-generated + :summary: List the token names of all generated creatures. + +.. dfhack-command:: save-generated-raws + :summary: Export a creature graphics file for modding. + +Now, forgotten beasts, titans, necromancer experiments, etc. will have raw token +names that match the description given in-game instead of unreadable generated +strings. Usage: diff --git a/docs/plugins/getplants.rst b/docs/plugins/getplants.rst index 21325e8f2..502f6e9ae 100644 --- a/docs/plugins/getplants.rst +++ b/docs/plugins/getplants.rst @@ -1,10 +1,12 @@ getplants ========= -**Tags:** `tag/fort`, `tag/productivity` -:dfhack-keybind:`getplants` -Designate trees for chopping and shrubs for gathering. Specify the types of -trees to cut down and/or shrubs to gather by their plant names. +.. dfhack-tool:: + :summary: Designate trees for chopping and shrubs for gathering. + :tags: fort productivity + +Specify the types of trees to cut down and/or shrubs to gather by their plant +names. Usage: diff --git a/docs/plugins/hotkeys.rst b/docs/plugins/hotkeys.rst index fbf4a9a76..7d30d486e 100644 --- a/docs/plugins/hotkeys.rst +++ b/docs/plugins/hotkeys.rst @@ -1,11 +1,12 @@ hotkeys ======= -**Tags:** `tag/system`, `tag/productivity`, `tag/interface` -:dfhack-keybind:`hotkeys` -Show all dfhack keybindings in current context. The command opens an in-game -screen showing which DFHack keybindings are active in the current context. -See also `hotkey-notes`. +.. dfhack-tool:: + :summary: Show all dfhack keybindings for the current context. + :tags: system productivity interface + +The command opens an in-game screen showing which DFHack keybindings are active +in the current context. See also `hotkey-notes`. Usage:: diff --git a/docs/plugins/infiniteSky.rst b/docs/plugins/infiniteSky.rst index ceb08aa0e..46f30c24c 100644 --- a/docs/plugins/infiniteSky.rst +++ b/docs/plugins/infiniteSky.rst @@ -1,10 +1,13 @@ infiniteSky =========== -**Tags:** `tag/fort`, `tag/map` -:dfhack-keybind:`infiniteSky` -Automatically allocates new z-levels of sky at the top of the map as you build -up, or on request allocates many levels all at once. +.. dfhack-tool:: + :summary: Automatically allocates new z-levels of sky + :tags: fort map + +If enabled, this plugin will automatically allocate new z-levels of sky at the +top of the map as you build up. Or it can allocate one or many additional levels +at your command. Usage: diff --git a/docs/plugins/isoworldremote.rst b/docs/plugins/isoworldremote.rst index 84babe66a..1fa439b29 100644 --- a/docs/plugins/isoworldremote.rst +++ b/docs/plugins/isoworldremote.rst @@ -1,5 +1,9 @@ isoworldremote ============== -**Tags:** `tag/dev`, `tag/mod` -Provides a `remote API ` used by Isoworld. +.. dfhack-tool:: + :summary: Provides a remote API used by Isoworld. + :tags: dev mod + :no-command: + +See `remote` for related remote APIs. diff --git a/docs/plugins/jobutils.rst b/docs/plugins/jobutils.rst index 5dcb0c85f..46b221f89 100644 --- a/docs/plugins/jobutils.rst +++ b/docs/plugins/jobutils.rst @@ -2,12 +2,20 @@ jobutils ======== -**Tags:** `tag/fort`, `tag/inspection`, `tag/jobs` -:dfhack-keybind:`job` -:dfhack-keybind:`job-duplicate` -:dfhack-keybind:`job-material` -Inspect or modify details of workshop jobs. +.. dfhack-tool:: + :summary: Provides commands for interacting with jobs. + :tags: fort inspection jobs + :no-command: + +.. dfhack-command:: job + :summary: Inspect or modify details of workshop jobs. + +.. dfhack-command:: job-duplicate + :summary: Duplicates the highlighted job. + +.. dfhack-command:: job-material + :summary: Alters the material of the selected job. Usage: diff --git a/docs/plugins/labormanager.rst b/docs/plugins/labormanager.rst index 1a25cedae..b168a3b7a 100644 --- a/docs/plugins/labormanager.rst +++ b/docs/plugins/labormanager.rst @@ -1,12 +1,14 @@ labormanager ============ -**Tags:** `tag/fort`, `tag/auto`, `tag/labors` -:dfhack-keybind:`labormanager` -Automatically manage dwarf labors. Labormanager is derived from `autolabor` -but uses a completely different approach to assigning jobs to dwarves. While -autolabor tries to keep as many dwarves busy as possible, labormanager instead -strives to get jobs done as quickly as possible. +.. dfhack-tool:: + :summary: Automatically manage dwarf labors. + :tags: fort auto labors + +Labormanager is derived from `autolabor` but uses a completely different +approach to assigning jobs to dwarves. While autolabor tries to keep as many +dwarves busy as possible, labormanager instead strives to get jobs done as +quickly as possible. Labormanager frequently scans the current job list, current list of dwarves, and the map to determine how many dwarves need to be assigned to what labors in diff --git a/docs/plugins/lair.rst b/docs/plugins/lair.rst index 43bb06072..f42cc0777 100644 --- a/docs/plugins/lair.rst +++ b/docs/plugins/lair.rst @@ -1,10 +1,11 @@ lair ==== -**Tags:** `tag/fort`, `tag/armok`, `tag/map` -:dfhack-keybind:`lair` -Mark the map as a monster lair. This avoids item scatter when the fortress is -abandoned. +.. dfhack-tool:: + :summary: Mark the map as a monster lair. + :tags: fort armok map + +This avoids item scatter when the fortress is abandoned. Usage: diff --git a/docs/plugins/liquids.rst b/docs/plugins/liquids.rst index 808089591..a498364fc 100644 --- a/docs/plugins/liquids.rst +++ b/docs/plugins/liquids.rst @@ -2,9 +2,13 @@ liquids ======= -**Tags:** `tag/adventure`, `tag/fort`, `tag/armok`, `tag/map` -:dfhack-keybind:`liquids` -:dfhack-keybind:`liquids-here` + +.. dfhack-tool:: + :summary: Place magma, water or obsidian. + :tags: adventure fort armok map + +.. dfhack-command:: liquids-here + :summary: Spawn liquids on the selected tile. Place magma, water or obsidian. See `gui/liquids` for an in-game interface for this functionality. diff --git a/docs/plugins/luasocket.rst b/docs/plugins/luasocket.rst index cbbf09f7e..24b30c7a6 100644 --- a/docs/plugins/luasocket.rst +++ b/docs/plugins/luasocket.rst @@ -1,7 +1,9 @@ luasocket ========= -**Tags:** `tag/dev`, `tag/mod` -Provides a Lua API for accessing network sockets. +.. dfhack-tool:: + :summary: Provides a Lua API for accessing network sockets. + :tags: dev mod + :no-command: See `luasocket-api` for details. diff --git a/docs/plugins/manipulator.rst b/docs/plugins/manipulator.rst index 07d1d4ab6..26223587d 100644 --- a/docs/plugins/manipulator.rst +++ b/docs/plugins/manipulator.rst @@ -1,9 +1,12 @@ manipulator =========== -**Tags:** `tag/fort`, `tag/productivity`, `tag/interface`, `tag/labors` -An in-game labor management interface. It is equivalent to the popular Dwarf -Therapist utility. +.. dfhack-tool:: + :summary: An in-game labor management interface. + :tags: fort productivity interface labors + :no-command: + +It is equivalent to the popular Dwarf Therapist utility. To activate, open the unit screen and press :kbd:`l`. diff --git a/docs/plugins/map-render.rst b/docs/plugins/map-render.rst index 6b7f1a5d9..7754af8b7 100644 --- a/docs/plugins/map-render.rst +++ b/docs/plugins/map-render.rst @@ -1,7 +1,9 @@ map-render ========== -**Tags:** `tag/dev` -Provides a Lua API for rerendering portions of the map. +.. dfhack-tool:: + :summary: Provides a Lua API for rerendering portions of the map. + :tags: dev + :no-command: See `map-render-api` for details. diff --git a/docs/plugins/misery.rst b/docs/plugins/misery.rst index 5d11f98cb..af71ae19a 100644 --- a/docs/plugins/misery.rst +++ b/docs/plugins/misery.rst @@ -1,9 +1,9 @@ misery ====== -**Tags:** `tag/fort`, `tag/armok`, `tag/units` -:dfhack-keybind:`misery` -Increase the intensity of negative dwarven thoughts. +.. dfhack-tool:: + :summary: Increase the intensity of negative dwarven thoughts. + :tags: fort armok units When enabled, negative thoughts that your dwarves have will multiply by the specified factor. diff --git a/docs/plugins/mode.rst b/docs/plugins/mode.rst index 8c33d6ec3..db94e6bac 100644 --- a/docs/plugins/mode.rst +++ b/docs/plugins/mode.rst @@ -1,9 +1,9 @@ mode ==== -**Tags:** `tag/dev` -:dfhack-keybind:`mode` -This command lets you see and change the game mode directly. +.. dfhack-tool:: + :summary: See and change the game mode. + :tags: dev .. warning:: diff --git a/docs/plugins/mousequery.rst b/docs/plugins/mousequery.rst index 7e01de6d0..eb6550d60 100644 --- a/docs/plugins/mousequery.rst +++ b/docs/plugins/mousequery.rst @@ -1,7 +1,9 @@ mousequery ========== -**Tags:** `tag/fort`, `tag/productivity`, `tag/interface` -:dfhack-keybind:`mousequery` + +.. dfhack-tool:: + :summary: Adds mouse controls to the DF interface. + :tags: fort productivity interface Adds mouse controls to the DF interface. For example, with ``mousequery`` you can click on buildings to configure them, hold the mouse button to draw dig diff --git a/docs/plugins/nestboxes.rst b/docs/plugins/nestboxes.rst index 1558a7357..9182eee09 100644 --- a/docs/plugins/nestboxes.rst +++ b/docs/plugins/nestboxes.rst @@ -1,11 +1,14 @@ nestboxes ========= -**Tags:** `tag/fort`, `tag/auto`, `tag/animals` -Protect fertile eggs incubating in a nestbox. This plugin will automatically -scan for and forbid fertile eggs incubating in a nestbox so that dwarves won't -come to collect them for eating. The eggs will hatch normally, even when -forbidden. +.. dfhack-tool:: + :summary: Protect fertile eggs incubating in a nestbox. + :tags: fort auto animals + :no-command: + +This plugin will automatically scan for and forbid fertile eggs incubating in a +nestbox so that dwarves won't come to collect them for eating. The eggs will +hatch normally, even when forbidden. Usage:: diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index 3be1f82b2..9403c6c8e 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -1,9 +1,9 @@ orders ====== -**Tags:** `tag/fort`, `tag/productivity`, `tag/jobs` -:dfhack-keybind:`orders` -Manage manager orders. +.. dfhack-tool:: + :summary: Manage manager orders. + :tags: fort productivity jobs Usage: diff --git a/docs/plugins/pathable.rst b/docs/plugins/pathable.rst index 6368d1c7e..800580017 100644 --- a/docs/plugins/pathable.rst +++ b/docs/plugins/pathable.rst @@ -1,8 +1,10 @@ pathable ======== -**Tags:** `tag/dev`, `tag/inspection`, `tag/interface`, `tag/map` -Marks tiles that are reachable from the cursor. This plugin provides a Lua API, -but no direct commands. +.. dfhack-tool:: + :summary: Marks tiles that are reachable from the cursor. + :tags: dev inspection interface map + :no-command: -See `pathable-api` for details. +This plugin provides a Lua API, but no direct commands. See `pathable-api` for +details. diff --git a/docs/plugins/petcapRemover.rst b/docs/plugins/petcapRemover.rst index 95a9e60bd..f3d011308 100644 --- a/docs/plugins/petcapRemover.rst +++ b/docs/plugins/petcapRemover.rst @@ -1,16 +1,17 @@ petcapRemover ============= -**Tags:** `tag/fort`, `tag/armok`, `tag/animals` -:dfhack-keybind:`petcapRemover` -Modify the pet population cap. In vanilla DF, pets will not reproduce unless the -population is below 50 and the number of children of that species is below a -certain percentage. This plugin allows removing these restrictions and setting -your own thresholds. Pets still require PET or PET_EXOTIC tags in order to -reproduce. In order to make population more stable and avoid sudden population -booms as you go below the raised population cap, this plugin counts pregnancies -toward the new population cap. It can still go over, but only in the case of -multiple births. +.. dfhack-tool:: + :summary: Modify the pet population cap. + :tags: fort armok animals + +In vanilla DF, pets will not reproduce unless the population is below 50 and the +number of children of that species is below a certain percentage. This plugin +allows removing these restrictions and setting your own thresholds. Pets still +require PET or PET_EXOTIC tags in order to reproduce. In order to make +population more stable and avoid sudden population booms as you go below the +raised population cap, this plugin counts pregnancies toward the new population +cap. It can still go over, but only in the case of multiple births. Usage: diff --git a/docs/plugins/plants.rst b/docs/plugins/plants.rst index 2021bc103..d28119af9 100644 --- a/docs/plugins/plants.rst +++ b/docs/plugins/plants.rst @@ -2,10 +2,14 @@ plants ====== -**Tags:** `tag/adventure`, `tag/fort`, `tag/armok`, `tag/map` -:dfhack-keybind:`plant` -Grow shrubs or trees. +.. dfhack-tool:: + :summary: Provides commands that interact with plants. + :tags: adventure fort armok map + :no-command: + +.. dfhack-command:: plant + :summary: Create a plant or make an existing plant grow up. Usage: diff --git a/docs/plugins/power-meter.rst b/docs/plugins/power-meter.rst index 72a49b72c..01e75491a 100644 --- a/docs/plugins/power-meter.rst +++ b/docs/plugins/power-meter.rst @@ -1,7 +1,11 @@ power-meter =========== -**Tags:** `tag/fort`, `tag/mod`, `tag/buildings` -Allow presure plates to measure power. If you run `gui/power-meter` while -building a pressure plate, the pressure plate can be modified to detect power -being supplied to gear boxes built in the four adjacent N/S/W/E tiles. +.. dfhack-tool:: + :summary: Allow presure plates to measure power. + :tags: fort mod buildings + :no-command: + +If you run `gui/power-meter` while building a pressure plate, the pressure +plate can be modified to detect power being supplied to gear boxes built in the +four adjacent N/S/W/E tiles. diff --git a/docs/plugins/probe.rst b/docs/plugins/probe.rst index 0285bb821..54436be68 100644 --- a/docs/plugins/probe.rst +++ b/docs/plugins/probe.rst @@ -1,11 +1,15 @@ probe ===== -**Tags:** `tag/adventure`, `tag/fort`, `tag/inspection`, `tag/map` -:dfhack-keybind:`probe` -:dfhack-keybind:`bprobe` -:dfhack-keybind:`cprobe` -Display low-level properties of selected objects. +.. dfhack-tool:: + :summary: Display low-level properties of the selected tile. + :tags: adventure fort inspection map + +.. dfhack-command:: bprobe + :summary: Display low-level properties of the selected building. + +.. dfhack-command:: cprobe + :summary: Display low-level properties of the selected unit. Usage: diff --git a/docs/plugins/prospector.rst b/docs/plugins/prospector.rst index 7ff3b4313..dc6204c06 100644 --- a/docs/plugins/prospector.rst +++ b/docs/plugins/prospector.rst @@ -2,11 +2,17 @@ prospector ========== -**Tags:** `tag/fort`, `tag/embark`, `tag/inspection`, `tag/map` -:dfhack-keybind:`prospect` -Shows a summary of resources that exist on the map. It can also calculate an -estimate of resources available in the selected embark area. +.. dfhack-tool:: + :summary: Provides commands that help you analyze natural resources. + :tags: fort embark inspection map + :no-command: + +.. dfhack-command:: prospect + :summary: Shows a summary of resources that exist on the map. + +It can also calculate an estimate of resources available in the selected embark +area. Usage:: diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst index 98590a73e..72f077fcd 100644 --- a/docs/plugins/regrass.rst +++ b/docs/plugins/regrass.rst @@ -1,14 +1,15 @@ regrass ======= -**Tags:** `tag/adventure`, `tag/fort`, `tag/armok`, `tag/animals` -:dfhack-keybind:`regrass` -Regrows all the grass. Use this command if your grazers have eaten everything -down to the dirt. +.. dfhack-tool:: + :summary: Regrows all the grass. + :tags: adventure fort armok animals + +Use this command if your grazers have eaten everything down to the dirt. Usage:: regrass [max] -Specify the 'max' option to pack more grass onto a tile than what the game +Specify the 'max' keyword to pack more grass onto a tile than what the game normally allows to give your grazers extra chewing time. diff --git a/docs/plugins/rename.rst b/docs/plugins/rename.rst index 34695e19a..02bc04c2c 100644 --- a/docs/plugins/rename.rst +++ b/docs/plugins/rename.rst @@ -1,9 +1,11 @@ rename ====== -**Tags:** `tag/adventure`, `tag/fort`, `tag/productivity` -:dfhack-keybind:`rename` -Easily rename things. Use `gui/rename` for an in-game interface. +.. dfhack-tool:: + :summary: Easily rename things. + :tags: adventure fort productivity + +Use `gui/rename` for an in-game interface. Usage: diff --git a/docs/plugins/rendermax.rst b/docs/plugins/rendermax.rst index 83e9ee461..680676fa3 100644 --- a/docs/plugins/rendermax.rst +++ b/docs/plugins/rendermax.rst @@ -1,10 +1,12 @@ rendermax ========= -**Tags:** `tag/adventure`, `tag/fort`, `tag/mod` -:dfhack-keybind:`rendermax` -Modify the map lighting. This plugin provides a collection of OpenGL lighting -filters that affect how the map is drawn to the screen. +.. dfhack-tool:: + :summary: Modify the map lighting. + :tags: adventure fort mod + +This plugin provides a collection of OpenGL lighting filters that affect how the +map is drawn to the screen. Usage: diff --git a/docs/plugins/resume.rst b/docs/plugins/resume.rst index 2f28d7083..0713026e4 100644 --- a/docs/plugins/resume.rst +++ b/docs/plugins/resume.rst @@ -1,13 +1,18 @@ resume ====== -**Tags:** `tag/fort`, `tag/productivity` -:dfhack-keybind:`resume` - -Color planned buildings based on their suspend status. When enabled, this plugin -will display a colored 'X' over suspended buildings. When run as a command, it -can resume all suspended building jobs, allowing you to quickly recover if a -bunch of jobs were suspended due to the workers getting scared off by wildlife -or items temporarily blocking buildling sites. + +.. dfhack-tool:: + :summary: Color planned buildings based on their suspend status. + :tags: fort productivity + :no-command: + +.. dfhack-command:: resume + :summary: Resume all suspended building jobs. + +When enabled, this plugin will display a colored 'X' over suspended buildings. +When run as a command, it can resume all suspended building jobs, allowing you +to quickly recover if a bunch of jobs were suspended due to the workers getting +scared off by wildlife or items temporarily blocking buildling sites. Usage:: diff --git a/docs/plugins/reveal.rst b/docs/plugins/reveal.rst index fb69d9a02..122280a1e 100644 --- a/docs/plugins/reveal.rst +++ b/docs/plugins/reveal.rst @@ -2,17 +2,29 @@ reveal ====== -**Tags:** `tag/adventure`, `tag/fort`, `tag/inspection`, `tag/armok`, `tag/map` -:dfhack-keybind:`reveal` -:dfhack-keybind:`unreveal` -:dfhack-keybind:`revforget` -:dfhack-keybind:`revtoggle` -:dfhack-keybind:`revflood` -:dfhack-keybind:`nopause` - -Reveals the map. This reveals all z-layers in fort mode. It also works in -adventure mode, but any of its effects are negated once you move. When you use -it this way, you don't need to run ``unreveal`` to hide the map again. + +.. dfhack-tool:: + :summary: Reveals the map. + :tags: adventure fort inspection armok map + +.. dfhack-tool:: unreveal + :summary: Hides previously hidden tiles again. + +.. dfhack-tool:: revforget + :summary: Discard records about what was visible before revealing the map. + +.. dfhack-tool:: revtoggle + :summary: Switch between reveal and unreveal. + +.. dfhack-tool:: revflood + :summary: Hide everything, then reveal tiles with a path to the cursor. + +.. dfhack-tool:: nopause + :summary: Disable pausing. + +This reveals all z-layers in fort mode. It also works in adventure mode, but any +of its effects are negated once you move. When you use it this way, you don't +need to run ``unreveal`` to hide the map again. Usage: diff --git a/docs/plugins/ruby.rst b/docs/plugins/ruby.rst index d3b89d3a9..f808735d4 100644 --- a/docs/plugins/ruby.rst +++ b/docs/plugins/ruby.rst @@ -2,12 +2,17 @@ ruby ==== -**Tags:** `tag/dev` -:dfhack-keybind:`rb` -:dfhack-keybind:`rb_eval` -Allow Ruby scripts to be executed. When invoked as a command, you can Eval() a -ruby string. +.. dfhack-tool:: + :summary: Allow Ruby scripts to be executed as DFHack commands. + :tags: dev + :no-command: + +.. dfhack-command:: rb + :summary: Eval() a ruby string. + +.. dfhack-command:: rb_eval + :summary: Eval() a ruby string. Usage:: diff --git a/docs/plugins/search.rst b/docs/plugins/search.rst index 41ae1c922..937e09885 100644 --- a/docs/plugins/search.rst +++ b/docs/plugins/search.rst @@ -2,12 +2,16 @@ search ====== -**Tags:** `tag/fort`, `tag/productivity`, `tag/interface` -Adds search capabilities to the UI. The Stocks, Animals, Trading, Stockpile, -Noble (assignment candidates), Military (position candidates), Burrows (unit -list), Rooms, Announcements, Job List, and Unit List screens all get hotkeys -that allow you to dynamically filter the displayed lists. +.. dfhack-tool:: + :summary: Adds search capabilities to the UI. + :tags: fort productivity interface + :no-command: + +Search options are added to the Stocks, Animals, Trading, Stockpile, Noble +aassignment candidates), Military (position candidates), Burrows (unit list), +Rooms, Announcements, Job List, and Unit List screens all get hotkeys that allow +you to dynamically filter the displayed lists. Usage:: diff --git a/docs/plugins/seedwatch.rst b/docs/plugins/seedwatch.rst index bd51fdd2e..1aaa04ee3 100644 --- a/docs/plugins/seedwatch.rst +++ b/docs/plugins/seedwatch.rst @@ -1,9 +1,9 @@ seedwatch ========= -**Tags:** `tag/fort`, `tag/auto` -:dfhack-keybind:`seedwatch` -Manages seed and plant cooking based on seed stock levels. +.. dfhack-tool:: + :summary: Manages seed and plant cooking based on seed stock levels. + :tags: fort auto Each seed type can be assigned a target. If the number of seeds of that type falls below that target, then the plants and seeds of that type will be excluded diff --git a/docs/plugins/showmood.rst b/docs/plugins/showmood.rst index 42af0e7a8..da67b5803 100644 --- a/docs/plugins/showmood.rst +++ b/docs/plugins/showmood.rst @@ -1,9 +1,9 @@ showmood ======== -**Tags:** `tag/fort`, `tag/inspection`, `tag/jobs` -:dfhack-keybind:`showmood` -Shows all items needed for the active strange mood. +.. dfhack-tool:: + :summary: Shows all items needed for the active strange mood. + :tags: fort inspection jobs Usage:: diff --git a/docs/plugins/siege-engine.rst b/docs/plugins/siege-engine.rst index 9255110bd..06ff963b7 100644 --- a/docs/plugins/siege-engine.rst +++ b/docs/plugins/siege-engine.rst @@ -1,10 +1,13 @@ siege-engine ============ -**Tags:** `tag/fort`, `tag/mod`, `tag/buildings` -Extend the functionality and usability of siege engines. Siege engines in DF -haven't been updated since the game was 2D, and can only aim in four -directions. To make them useful above-ground, this plugin allows you to: +.. dfhack-tool:: + :summary: Extend the functionality and usability of siege engines. + :tags: fort mod buildings + :no-command: + +Siege engines in DF haven't been updated since the game was 2D, and can only aim +in four directions. To make them useful above-ground, this plugin allows you to: * link siege engines to stockpiles * restrict operator skill levels (like workshops) diff --git a/docs/plugins/sort.rst b/docs/plugins/sort.rst index f3ba6e6fb..c746f1c79 100644 --- a/docs/plugins/sort.rst +++ b/docs/plugins/sort.rst @@ -1,10 +1,16 @@ sort ==== -**Tags:** `tag/fort`, `tag/productivity`, `tag/interface` -:dfhack-keybind:`sort-items` -:dfhack-keybind:`sort-units` -Sort the visible item or unit list. +.. dfhack-tool:: + :summary: Sort lists shown in the DF interface. + :tags: fort productivity interface + :no-command: + +.. dfhack-command:: sort-items + :summary: Sort the visible item list. + +.. dfhack-command:: sort-units + :summary: Sort the visible unit list. Usage:: diff --git a/docs/plugins/spectate.rst b/docs/plugins/spectate.rst index 08cc3f819..7f448ebfb 100644 --- a/docs/plugins/spectate.rst +++ b/docs/plugins/spectate.rst @@ -1,8 +1,10 @@ spectate ======== -**Tags:** `tag/fort`, `tag/units` -Automatically follow exciting dwarves. +.. dfhack-tool:: + :summary: Automatically follow exciting dwarves. + :tags: fort units + :no-command: Usage:: diff --git a/docs/plugins/steam-engine.rst b/docs/plugins/steam-engine.rst index 0fe245d63..e0f938915 100644 --- a/docs/plugins/steam-engine.rst +++ b/docs/plugins/steam-engine.rst @@ -1,10 +1,13 @@ steam-engine ============ -**Tags:** `tag/fort`, `tag/mod`, `tag/buildings` -Allow modded steam engine buildings to function. The steam-engine plugin detects -custom workshops with STEAM_ENGINE in their token, and turns them into real -steam engines! +.. dfhack-tool:: + :summary: Allow modded steam engine buildings to function. + :tags: fort mod buildings + :no-command: + +The steam-engine plugin detects custom workshops with the string +``STEAM_ENGINE`` in their token, and turns them into real steam engines! The plugin auto-enables itself when it detects the relevant tags in the world raws. It does not need to be enabled with the `enable` command. diff --git a/docs/plugins/stockflow.rst b/docs/plugins/stockflow.rst index 48647b869..81eab07ff 100644 --- a/docs/plugins/stockflow.rst +++ b/docs/plugins/stockflow.rst @@ -1,11 +1,13 @@ stockflow ========= -**Tags:** `tag/fort`, `tag/auto`, `tag/jobs`, `tag/stockpiles` -:dfhack-keybind:`stockflow` -Queue manager jobs based on free space in stockpiles. With this plugin, the -fortress bookkeeper can tally up free space in specific stockpiles and queue -jobs through the manager to produce items to fill the free space. +.. dfhack-tool:: + :summary: Queue manager jobs based on free space in stockpiles. + :tags: fort auto jobs stockpiles + +With this plugin, the fortress bookkeeper can tally up free space in specific +stockpiles and queue jobs through the manager to produce items to fill the free +space. When the plugin is enabled, the :kbd:`q` menu of each stockpile will have two new options: diff --git a/docs/plugins/stockpiles.rst b/docs/plugins/stockpiles.rst index 0c1000456..0e30b9dc7 100644 --- a/docs/plugins/stockpiles.rst +++ b/docs/plugins/stockpiles.rst @@ -2,14 +2,24 @@ stockpiles ========== -**Tags:** `tag/fort`, `tag/productivity`, `tag/design`, `tag/stockpiles` -:dfhack-keybind:`copystock` -:dfhack-keybind:`savestock` -:dfhack-keybind:`loadstock` - -Import and export stockpile settings. When the plugin is enabled, the :kbd:`q` -menu of each stockpile will have an option for saving or loading the stockpile -settings. See `gui/stockpiles` for an in-game interface. + +.. dfhack-tool:: + :summary: Import and export stockpile settings. + :tags: fort productivity design stockpiles + :no-command: + +.. dfhack-command:: copystock + :summary: Copies the configuration of the selected stockpile. + +.. dfhack-command:: savestock + :summary: Exports the configuration of the selected stockpile. + +.. dfhack-command:: loadstock + :summary: Imports the configuration of the selected stockpile. + +When the plugin is enabled, the :kbd:`q` menu of each stockpile will have an +option for saving or loading the stockpile settings. See `gui/stockpiles` for +an in-game interface. Usage: diff --git a/docs/plugins/stocks.rst b/docs/plugins/stocks.rst index 5506b1d28..e518fc1f1 100644 --- a/docs/plugins/stocks.rst +++ b/docs/plugins/stocks.rst @@ -1,10 +1,11 @@ stocks ====== -**Tags:** `tag/fort`, `tag/productivity`, `tag/interface` -:dfhack-keybind:`stocks` -Enhanced fortress stock management interface. When the plugin is enabled, two -new hotkeys become available: +.. dfhack-tool:: + :summary: Enhanced fortress stock management interface. + :tags: fort productivity interface + +When the plugin is enabled, two new hotkeys become available: * :kbd:`e` on the vanilla DF stocks screen (:kbd:`z` and then select Stocks) will launch the fortress-wide stock management screen. diff --git a/docs/plugins/stonesense.rst b/docs/plugins/stonesense.rst index 3bb271477..0315c92ed 100644 --- a/docs/plugins/stonesense.rst +++ b/docs/plugins/stonesense.rst @@ -1,10 +1,12 @@ stonesense ========== -**Tags:** `tag/adventure`, `tag/fort`, `tag/interface`, `tag/map` -:dfhack-keybind:`stonesense` -:dfhack-keybind:`ssense` -A 3D isometric visualizer that runs in a second window. +.. dfhack-tool:: + :summary: A 3D isometric visualizer. + :tags: adventure fort interface map + +.. dfhack-command:: ssense + :summary: An alias for stonesense. Usage: diff --git a/docs/plugins/strangemood.rst b/docs/plugins/strangemood.rst index 0198af136..8d1fc8081 100644 --- a/docs/plugins/strangemood.rst +++ b/docs/plugins/strangemood.rst @@ -1,9 +1,9 @@ strangemood =========== -**Tags:** `tag/fort`, `tag/armok`, `tag/units` -:dfhack-keybind:`strangemood` -Triggers a strange mood. +.. dfhack-tool:: + :summary: Trigger a strange mood. + :tags: fort armok units Usage:: diff --git a/docs/plugins/tailor.rst b/docs/plugins/tailor.rst index 93072fb1b..5da293fbf 100644 --- a/docs/plugins/tailor.rst +++ b/docs/plugins/tailor.rst @@ -1,13 +1,14 @@ tailor ====== -**Tags:** `tag/fort`, `tag/auto`, `tag/jobs` -:dfhack-keybind:`tailor` - -Automatically keep your dwarves in fresh clothing. 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. + +.. dfhack-tool:: + :summary: Automatically keep your dwarves in fresh clothing. + :tags: fort auto jobs + +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. If there are not enough clothes available, manager orders will be generated to manufacture some more. ``tailor`` will intelligently create orders using diff --git a/docs/plugins/tiletypes.rst b/docs/plugins/tiletypes.rst index 185f283db..ee3d4a02f 100644 --- a/docs/plugins/tiletypes.rst +++ b/docs/plugins/tiletypes.rst @@ -3,15 +3,23 @@ tiletypes ========= -**Tags:** `tag/adventure`, `tag/fort`, `tag/armok`, `tag/map` -:dfhack-keybind:`tiletypes` -:dfhack-keybind:`tiletypes-command` -:dfhack-keybind:`tiletypes-here` -:dfhack-keybind:`tiletypes-here-point` - -Paints tiles of specified types onto the map. You can use the `probe` command -to discover properties of existing tiles that you'd like to copy. If you -accidentally paint over a vein that you want back, `fixveins` may help. + +.. dfhack-tool:: + :summary: Paints tiles of specified types onto the map. + :tags: adventure fort armok map + +.. dfhack-command:: tiletypes-command + :summary: Run tiletypes commands. + +.. dfhack-command:: tiletypes-here + :summary: Paint map tiles starting from the cursor. + +.. dfhack-command:: tiletypes-here-point + :summary: Paint the map tile under the cursor. + +You can use the `probe` command to discover properties of existing tiles that +you'd like to copy. If you accidentally paint over a vein that you want back, +`fixveins` may help. The tool works with a brush, a filter, and a paint specification. The brush determines the shape of the area to affect, the filter selects which tiles to diff --git a/docs/plugins/title-folder.rst b/docs/plugins/title-folder.rst index beddfc9cb..56de7d769 100644 --- a/docs/plugins/title-folder.rst +++ b/docs/plugins/title-folder.rst @@ -1,8 +1,10 @@ title-folder ============= -**Tags:** `tag/system`, `tag/interface` -Displays the DF folder name in the window title bar. +.. dfhack-tool:: + :summary: Displays the DF folder name in the window title bar. + :tags: system interface + :no-command: Usage:: diff --git a/docs/plugins/title-version.rst b/docs/plugins/title-version.rst index d9d35a85f..f7fc238c7 100644 --- a/docs/plugins/title-version.rst +++ b/docs/plugins/title-version.rst @@ -1,8 +1,10 @@ title-version ============= -**Tags:** `tag/system`, `tag/interface` -Displays the DFHack version on DF's title screen. +.. dfhack-tool:: + :summary: Displays the DFHack version on DF's title screen. + :tags: system interface + :no-command: Usage:: diff --git a/docs/plugins/trackstop.rst b/docs/plugins/trackstop.rst index cf5a9853f..65aae2b7f 100644 --- a/docs/plugins/trackstop.rst +++ b/docs/plugins/trackstop.rst @@ -1,11 +1,15 @@ trackstop ========= -**Tags:** `tag/fort`, `tag/interface`, `tag/mod`, `tag/buildings` -Adds dynamic configuration options for track stops. When enabled, this plugin -adds a :kbd:`q` menu for track stops, which is completely blank in vanilla DF. -This allows you to view and/or change the track stop's friction and dump -direction settings, using the keybindings from the track stop building interface. +.. dfhack-tool:: + :summary: Add dynamic configuration options for track stops. + :tags: fort interface mod buildings + :no-command: + +When enabled, this plugin adds a :kbd:`q` menu for track stops, which is +completely blank in vanilla DF. This allows you to view and/or change the track +stop's friction and dump direction settings, using the keybindings from the +track stop building interface. Usage:: diff --git a/docs/plugins/tubefill.rst b/docs/plugins/tubefill.rst index 404a180b2..865e4341d 100644 --- a/docs/plugins/tubefill.rst +++ b/docs/plugins/tubefill.rst @@ -1,9 +1,11 @@ tubefill ======== -**Tags:** `tag/fort`, `tag/armok`, `tag/map` -:dfhack-keybind:`tubefill` -Replentishes mined-out adamantine. Veins that were hollow will be left alone. +.. dfhack-tool:: + :summary: Replentishes mined-out adamantine. + :tags: fort armok map + +Veins that were originally hollow will be left alone. Usage:: diff --git a/docs/plugins/tweak.rst b/docs/plugins/tweak.rst index 03489845e..71b564434 100644 --- a/docs/plugins/tweak.rst +++ b/docs/plugins/tweak.rst @@ -1,9 +1,9 @@ tweak ===== -**Tags:** `tag/adventure`, `tag/fort`, `tag/interface`, `tag/fps`, `tag/fix`, `tag/armok` -:dfhack-keybind:`tweak` -Contains various tweaks for minor bugs. +.. dfhack-tool:: + :summary: A collection of tweaks and bugfixes. + :tags: adventure fort interface fps fix armok Usage:: diff --git a/docs/plugins/workNow.rst b/docs/plugins/workNow.rst index 895556ce3..678649487 100644 --- a/docs/plugins/workNow.rst +++ b/docs/plugins/workNow.rst @@ -1,11 +1,13 @@ workNow ======= -**Tags:** `tag/fort`, `tag/auto`, `tag/jobs` -:dfhack-keybind:`workNow` -Reduce the time that dwarves idle after completing a job. After finishing a job, -dwarves will wander away for a while before picking up a new job. This plugin -will automatically poke the game to assign dwarves to new tasks. +.. dfhack-tool:: + :summary: Reduce the time that dwarves idle after completing a job. + :tags: fort auto jobs + +After finishing a job, dwarves will wander away for a while before picking up a +new job. This plugin will automatically poke the game to assign dwarves to new +tasks. Usage: diff --git a/docs/plugins/workflow.rst b/docs/plugins/workflow.rst index c2a9ea26a..db615c876 100644 --- a/docs/plugins/workflow.rst +++ b/docs/plugins/workflow.rst @@ -1,8 +1,12 @@ workflow ======== -**Tags:** `tag/fort`, `tag/auto`, `tag/jobs` -:dfhack-keybind:`workflow` -:dfhack-keybind:`fix-job-postings` + +.. dfhack-tool:: + :summary: Manage repeat jobs according to stock levels. + :tags: fort auto jobs + +.. dfhack-command:: fix-job-postings + :summary: Fixes crashes caused by old versions of workflow. Manage repeat jobs according to stock levels. `gui/workflow` provides a simple front-end integrated in the game UI. @@ -55,7 +59,7 @@ Usage: can be copied to a file, and then reloaded using the `script` built-in command. ``fix-job-postings [dry-run]`` - Fixes crashes caused the version of workflow released with DFHack + Fixes crashes caused by the version of workflow released with DFHack 0.40.24-r4. It will be run automatically if needed. If your save has never been run with this version, you will never need this command. Specify the ``dry-run`` keyword to see what this command would do without making any diff --git a/docs/plugins/xlsxreader.rst b/docs/plugins/xlsxreader.rst index 123d9920a..3d6cc0ac8 100644 --- a/docs/plugins/xlsxreader.rst +++ b/docs/plugins/xlsxreader.rst @@ -1,7 +1,9 @@ xlsxreader ========== -**Tags:** `tag/dev` -Provides a Lua API for reading xlsx files. +.. dfhack-tool:: + :summary: Provides a Lua API for reading xlsx files. + :tags: dev + :no-command: See `xlsxreader-api` for details. diff --git a/docs/plugins/zone.rst b/docs/plugins/zone.rst index 9aa34d746..bf9428a6c 100644 --- a/docs/plugins/zone.rst +++ b/docs/plugins/zone.rst @@ -1,9 +1,9 @@ zone ==== -**Tags:** `tag/fort`, `tag/productivity`, `tag/animals`, `tag/buildings` -:dfhack-keybind:`zone` -Manage activity zones, cages, and the animals therein. +.. dfhack-tool:: + :summary: Manage activity zones, cages, and the animals therein. + :tags: fort productivity animals buildings Usage: diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index ff4bfc9b1..a051b76af 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -117,6 +117,7 @@ class DFHackToolDirective(DFHackToolDirectiveBase): option_spec = { 'tags': dfhack.util.directive_arg_str_list, 'no-command': rst_directives.flag, + 'summary': rst_directives.unchanged, } def render_content(self) -> List[nodes.Node]: @@ -134,9 +135,12 @@ class DFHackToolDirective(DFHackToolDirectiveBase): ] tag_nodes.pop() - return [ + ret_nodes = [ nodes.paragraph('', '', *tag_nodes), ] + if 'no-command' in self.options: + ret_nodes += [nodes.inline(text=self.options.get('summary', ''))] + return ret_nodes def run(self): out = DFHackToolDirectiveBase.run(self) @@ -146,10 +150,15 @@ class DFHackToolDirective(DFHackToolDirectiveBase): class DFHackCommandDirective(DFHackToolDirectiveBase): + option_spec = { + 'summary': rst_directives.unchanged_required, + } + def render_content(self) -> List[nodes.Node]: command = self.get_name_or_docname() return [ self.make_labeled_paragraph('Command', command, content_class=nodes.literal), + nodes.inline(text=self.options.get('summary', '')), *render_dfhack_keybind(command), ] From a01114a41bbe6cb19edb88bcd566c32dbecd2f62 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 13 Aug 2022 07:17:18 +0000 Subject: [PATCH 479/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index f37c0d022..4c7eecb5c 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit f37c0d022135ca10fb5375c4e56e11215bf208ae +Subproject commit 4c7eecb5cbeeead3c4374fafc562ee774e8e229e From b21fc8aa75debfd8416eca92d0b7604b3f3fa698 Mon Sep 17 00:00:00 2001 From: Myk Date: Sat, 13 Aug 2022 13:32:45 -0700 Subject: [PATCH 480/854] remove extra space at bottom of tool summary --- docs/styles/dfhack.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/styles/dfhack.css b/docs/styles/dfhack.css index 4102e6412..0fe608dde 100644 --- a/docs/styles/dfhack.css +++ b/docs/styles/dfhack.css @@ -71,3 +71,7 @@ div.dfhack-tool-summary p { margin-bottom: 0.5em; line-height: 1em; } + +div.dfhack-tool-summary p:last-child { + margin-bottom: 0; +} From f6699c0014dd231f8d2dc205183d9f5a0098dfc4 Mon Sep 17 00:00:00 2001 From: Myk Date: Sat, 13 Aug 2022 13:40:14 -0700 Subject: [PATCH 481/854] Sync tags descriptions from spreadsheet --- docs/Tags.rst | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/Tags.rst b/docs/Tags.rst index d0c785c3c..96faf74e2 100644 --- a/docs/Tags.rst +++ b/docs/Tags.rst @@ -3,25 +3,27 @@ - `tag/adventure`: Tools that are useful while in adventure mode. Note that some tools only tagged with "fort" might also work in adventure mode, but not always in expected ways. Feel free to experiment, though! - `tag/fort`: Tools that are useful while in fort mode. - `tag/legends`: Tools that are useful while in legends mode. -- `tag/embark`: Tools that are useful while on the embark screen. -- `tag/system`: Tools related to working with DFHack commands or the core DFHack library. -- `tag/dev`: Tools useful for develpers and modders. -- `tag/auto`: Tools that you turn on and then they automatically manage some aspect of your fortress. +- `tag/embark`: Tools that are useful while on the fort embark screen or while creating an adventurer. +- `tag/dev`: Tools that are useful when developing scripts or mods. +- `tag/dfhack`: Tools that you use to run DFHack commands or interact with the DFHack library. This tag also includes tools that help you manage the DF game itself (e.g. settings, saving, etc.) +- `tag/auto`: Tools that run in the background and automatically manage routine, toilsome aspects of your fortress. - `tag/productivity`: Tools that help you do things that you could do manually, but using the tool is better and faster. -- `tag/inspection`: Tools that let you inspect game data. +- `tag/inspection`: Tools that let you view information that is otherwise difficult to find. - `tag/design`: Tools that help you design your fort. -- `tag/quickfort`: Tools that are involved in creating and playing back blueprints. -- `tag/interface`: Tools that modify or extend the user interface. +- `tag/gameplay`: Tools that introduce new gameplay elements. - `tag/fps`: Tools that help you manage FPS drop. -- `tag/fix`: Tools that fix specific bugs. -- `tag/mod`: Tools that introduce new gameplay elements. -- `tag/armok`: Tools that give you complete control over various aspects of the game. -- `tag/animals`: Tools that help you manage animals. -- `tag/buildings`: Tools that help you work with placing or configuring buildings and furniture. -- `tag/items`: Tools that create or modify in-game items. -- `tag/jobs`: Tools that create or modify jobs. -- `tag/map`: Map modification. -- `tag/labors`: Tools that deal with labor assignment. -- `tag/units`: Tools that create or modify units. +- `tag/bugfix`: Tools that fix specific bugs, either permanently or on-demand. +- `tag/armok`: Tools that give you complete control over an aspect of the game or provide access to information that the game intentionally keeps hidden. +- `tag/animals`: Tools that interact with animals. +- `tag/buildings`: Tools that interact with buildings and furniture. - `tag/stockpiles`: Tools that interact wtih stockpiles. -- `tag/trees`: Tools that interact with trees and shrubs. +- `tag/items`: Tools that interact with in-game items. +- `tag/map`: Tools that interact with the game map. +- `tag/interface`: Tools that interact with or extend the DF user interface. +- `tag/workorders`: Tools that interact with workorders. +- `tag/jobs`: Tools that interact with jobs. +- `tag/labors`: Tools that deal with labor assignment. +- `tag/military`: Tools that interact with the military. +- `tag/graphics`: Tools that interact with game graphics. +- `tag/plants`: Tools that interact with trees, shrubs, and crops. +- `tag/units`: Tools that interact with units. From eb91feff7c167b4b118b753022ad06ca4df66316 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 11 Aug 2022 23:34:37 -0700 Subject: [PATCH 482/854] revise tag list and assignments --- docs/builtins/alias.rst | 2 +- docs/builtins/cls.rst | 2 +- docs/builtins/die.rst | 2 +- docs/builtins/disable.rst | 2 +- docs/builtins/enable.rst | 2 +- docs/builtins/fpause.rst | 2 +- docs/builtins/help.rst | 2 +- docs/builtins/hide.rst | 2 +- docs/builtins/keybinding.rst | 2 +- docs/builtins/kill-lua.rst | 2 +- docs/builtins/load.rst | 2 +- docs/builtins/ls.rst | 2 +- docs/builtins/plug.rst | 2 +- docs/builtins/reload.rst | 2 +- docs/builtins/sc-script.rst | 2 +- docs/builtins/script.rst | 2 +- docs/builtins/show.rst | 2 +- docs/builtins/tags.rst | 2 +- docs/builtins/type.rst | 2 +- docs/builtins/unload.rst | 2 +- docs/plugins/3dveins.rst | 2 +- docs/plugins/RemoteFortressReader.rst | 2 +- docs/plugins/add-spatter.rst | 2 +- docs/plugins/autochop.rst | 2 +- docs/plugins/autoclothing.rst | 2 +- docs/plugins/autodump.rst | 2 +- docs/plugins/autofarm.rst | 2 +- docs/plugins/autogems.rst | 2 +- docs/plugins/automelt.rst | 2 +- docs/plugins/autotrade.rst | 2 +- docs/plugins/blueprint.rst | 2 +- docs/plugins/building-hacks.rst | 2 +- docs/plugins/buildingplan.rst | 2 +- docs/plugins/burrows.rst | 2 +- docs/plugins/cleanconst.rst | 2 +- docs/plugins/cleaners.rst | 2 +- docs/plugins/command-prompt.rst | 2 +- docs/plugins/cursecheck.rst | 2 +- docs/plugins/diggingInvaders.rst | 2 +- docs/plugins/dwarfmonitor.rst | 2 +- docs/plugins/dwarfvet.rst | 2 +- docs/plugins/eventful.rst | 2 +- docs/plugins/fix-unit-occupancy.rst | 2 +- docs/plugins/fixveins.rst | 2 +- docs/plugins/forceequip.rst | 2 +- docs/plugins/getplants.rst | 2 +- docs/plugins/hotkeys.rst | 2 +- docs/plugins/infiniteSky.rst | 2 +- docs/plugins/isoworldremote.rst | 2 +- docs/plugins/luasocket.rst | 2 +- docs/plugins/manipulator.rst | 2 +- docs/plugins/map-render.rst | 2 +- docs/plugins/mode.rst | 2 +- docs/plugins/orders.rst | 2 +- docs/plugins/pathable.rst | 2 +- docs/plugins/petcapRemover.rst | 2 +- docs/plugins/plants.rst | 2 +- docs/plugins/power-meter.rst | 2 +- docs/plugins/probe.rst | 2 +- docs/plugins/prospector.rst | 2 +- docs/plugins/regrass.rst | 2 +- docs/plugins/rename.rst | 2 +- docs/plugins/rendermax.rst | 2 +- docs/plugins/resume.rst | 2 +- docs/plugins/seedwatch.rst | 2 +- docs/plugins/showmood.rst | 2 +- docs/plugins/siege-engine.rst | 2 +- docs/plugins/spectate.rst | 2 +- docs/plugins/steam-engine.rst | 2 +- docs/plugins/stockflow.rst | 2 +- docs/plugins/stocks.rst | 2 +- docs/plugins/stonesense.rst | 2 +- docs/plugins/tailor.rst | 2 +- docs/plugins/title-folder.rst | 2 +- docs/plugins/title-version.rst | 2 +- docs/plugins/trackstop.rst | 2 +- docs/plugins/tweak.rst | 2 +- docs/plugins/workNow.rst | 2 +- 78 files changed, 78 insertions(+), 78 deletions(-) diff --git a/docs/builtins/alias.rst b/docs/builtins/alias.rst index 84a761556..a9f44386a 100644 --- a/docs/builtins/alias.rst +++ b/docs/builtins/alias.rst @@ -3,7 +3,7 @@ alias .. dfhack-tool:: :summary: Configure helper aliases for other DFHack commands. - :tags: system + :tags: dfhack Aliases are resolved immediately after built-in commands, which means that an alias cannot override a built-in command, but can override a command implemented diff --git a/docs/builtins/cls.rst b/docs/builtins/cls.rst index 2aad049b4..515235a93 100644 --- a/docs/builtins/cls.rst +++ b/docs/builtins/cls.rst @@ -3,7 +3,7 @@ cls .. dfhack-tool:: :summary: Clear the terminal screen. - :tags: system + :tags: dfhack Can also be invoked as ``clear``. Note that this command does not delete command history. It just clears the text on the screen. diff --git a/docs/builtins/die.rst b/docs/builtins/die.rst index b4873abc5..25a34504d 100644 --- a/docs/builtins/die.rst +++ b/docs/builtins/die.rst @@ -3,7 +3,7 @@ die .. dfhack-tool:: :summary: Instantly exit DF without saving. - :tags: system + :tags: dfhack Use to exit DF quickly and safely. diff --git a/docs/builtins/disable.rst b/docs/builtins/disable.rst index e109e69e1..73d267ccf 100644 --- a/docs/builtins/disable.rst +++ b/docs/builtins/disable.rst @@ -3,7 +3,7 @@ disable .. dfhack-tool:: :summary: Deactivate a DFHack tool that has some persistent effect. - :tags: system + :tags: dfhack See the `enable` command for more info. diff --git a/docs/builtins/enable.rst b/docs/builtins/enable.rst index 63b0bdc5f..19f98a122 100644 --- a/docs/builtins/enable.rst +++ b/docs/builtins/enable.rst @@ -3,7 +3,7 @@ enable .. dfhack-tool:: :summary: Activate a DFHack tool that has some persistent effect. - :tags: system + :tags: dfhack Many plugins and scripts can be in a distinct enabled or disabled state. Some of them activate and deactivate automatically depending on the contents of the diff --git a/docs/builtins/fpause.rst b/docs/builtins/fpause.rst index 2e8f851f7..5379caec0 100644 --- a/docs/builtins/fpause.rst +++ b/docs/builtins/fpause.rst @@ -3,7 +3,7 @@ fpause .. dfhack-tool:: :summary: Forces DF to pause. - :tags: system + :tags: dfhack This is useful when your FPS drops below 1 and you lose control of the game. diff --git a/docs/builtins/help.rst b/docs/builtins/help.rst index e1e913747..95dadefd9 100644 --- a/docs/builtins/help.rst +++ b/docs/builtins/help.rst @@ -3,7 +3,7 @@ help .. dfhack-tool:: :summary: Display help about a command or plugin. - :tags: system + :tags: dfhack Can also be invoked as ``?`` or ``man`` (short for "manual"). diff --git a/docs/builtins/hide.rst b/docs/builtins/hide.rst index 0ec8f36df..b23738860 100644 --- a/docs/builtins/hide.rst +++ b/docs/builtins/hide.rst @@ -3,7 +3,7 @@ hide .. dfhack-tool:: :summary: Hide the DFHack terminal window. - :tags: system + :tags: dfhack You can show it again with the `show` command, though you'll need to use it from a `keybinding` set beforehand or the in-game `command-prompt`. diff --git a/docs/builtins/keybinding.rst b/docs/builtins/keybinding.rst index 5ce8145f4..ae51d7a37 100644 --- a/docs/builtins/keybinding.rst +++ b/docs/builtins/keybinding.rst @@ -3,7 +3,7 @@ keybinding .. dfhack-tool:: :summary: Create hotkeys that will run DFHack commands. - :tags: system + :tags: dfhack Like any other command, it can be used at any time from the console, but bindings are not remembered between runs of the game unless re-created in diff --git a/docs/builtins/kill-lua.rst b/docs/builtins/kill-lua.rst index 4de0ab7d1..3334b3b2b 100644 --- a/docs/builtins/kill-lua.rst +++ b/docs/builtins/kill-lua.rst @@ -3,7 +3,7 @@ kill-lua .. dfhack-tool:: :summary: Gracefully stop any currently-running Lua scripts. - :tags: system + :tags: dfhack Use this command to stop a misbehaving script that appears to be stuck. diff --git a/docs/builtins/load.rst b/docs/builtins/load.rst index d253144a8..0c199ae3d 100644 --- a/docs/builtins/load.rst +++ b/docs/builtins/load.rst @@ -3,7 +3,7 @@ load .. dfhack-tool:: :summary: Load and register a plugin library. - :tags: system + :tags: dfhack Also see `unload` and `reload` for related actions. diff --git a/docs/builtins/ls.rst b/docs/builtins/ls.rst index 702c8bae5..6ef09d236 100644 --- a/docs/builtins/ls.rst +++ b/docs/builtins/ls.rst @@ -3,7 +3,7 @@ ls .. dfhack-tool:: :summary: List available DFHack commands. - :tags: system + :tags: dfhack In order to group related commands, each command is associated with a list of tags. You can filter the listed commands by a tag or a substring of the diff --git a/docs/builtins/plug.rst b/docs/builtins/plug.rst index e10c154e5..a1b136306 100644 --- a/docs/builtins/plug.rst +++ b/docs/builtins/plug.rst @@ -3,7 +3,7 @@ plug .. dfhack-tool:: :summary: List available plugins and whether they are enabled. - :tags: system + :tags: dfhack Usage:: diff --git a/docs/builtins/reload.rst b/docs/builtins/reload.rst index 1429e1038..17efffb2c 100644 --- a/docs/builtins/reload.rst +++ b/docs/builtins/reload.rst @@ -3,7 +3,7 @@ reload .. dfhack-tool:: :summary: Reload a loaded plugin. - :tags: system + :tags: dfhack Developers use this command to reload a plugin that they are actively modifying. Also see `load` and `unload` for related actions. diff --git a/docs/builtins/sc-script.rst b/docs/builtins/sc-script.rst index b3255cb9f..5203f1aff 100644 --- a/docs/builtins/sc-script.rst +++ b/docs/builtins/sc-script.rst @@ -3,7 +3,7 @@ sc-script .. dfhack-tool:: :summary: Run commands when game state changes occur. - :tags: system + :tags: dfhack This is similar to the static `init-files` but is slightly more flexible since it can be set dynamically. diff --git a/docs/builtins/script.rst b/docs/builtins/script.rst index fbcebff6c..0f40df5df 100644 --- a/docs/builtins/script.rst +++ b/docs/builtins/script.rst @@ -3,7 +3,7 @@ script .. dfhack-tool:: :summary: Execute a batch file of DFHack commands. - :tags: system + :tags: dfhack It reads a text file and runs each line as a DFHack command as if it had been typed in by the user -- treating the input like `an init file `. diff --git a/docs/builtins/show.rst b/docs/builtins/show.rst index 7002d5aff..c59f4e519 100644 --- a/docs/builtins/show.rst +++ b/docs/builtins/show.rst @@ -3,7 +3,7 @@ show .. dfhack-tool:: :summary: Unhides the DFHack terminal window. - :tags: system + :tags: dfhack Useful if you have hidden the terminal with `hide` and you want it back. Since the terminal window won't be available to run this command, you'll need to use diff --git a/docs/builtins/tags.rst b/docs/builtins/tags.rst index 93a094149..3823a9924 100644 --- a/docs/builtins/tags.rst +++ b/docs/builtins/tags.rst @@ -3,7 +3,7 @@ tags .. dfhack-tool:: :summary: List the strings that DFHack tools can be tagged with. - :tags: system + :tags: dfhack You can find groups of related tools by passing the tag name to the `ls` command. diff --git a/docs/builtins/type.rst b/docs/builtins/type.rst index 3336cae5f..dfcb493f9 100644 --- a/docs/builtins/type.rst +++ b/docs/builtins/type.rst @@ -3,7 +3,7 @@ type .. dfhack-tool:: :summary: Describe how a command is implemented. - :tags: system + :tags: dfhack DFHack commands can be provided by plugins, scripts, or by the core library itself. The ``type`` command can tell you which is the source of a particular diff --git a/docs/builtins/unload.rst b/docs/builtins/unload.rst index 132732a7f..fd9e11a46 100644 --- a/docs/builtins/unload.rst +++ b/docs/builtins/unload.rst @@ -3,7 +3,7 @@ unload .. dfhack-tool:: :summary: Unload a plugin from memory. - :tags: system + :tags: dfhack Also see `load` and `reload` for related actions. diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index c60582310..b22ac81e5 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -3,7 +3,7 @@ .. dfhack-tool:: :summary: Rewrite layer veins to expand in 3D space. - :tags: fort mod map + :tags: fort gameplay map Existing, flat veins are removed and new 3D veins that naturally span z-levels are generated in their place. The transformation preserves the mineral counts diff --git a/docs/plugins/RemoteFortressReader.rst b/docs/plugins/RemoteFortressReader.rst index f849266c2..9a925d0fd 100644 --- a/docs/plugins/RemoteFortressReader.rst +++ b/docs/plugins/RemoteFortressReader.rst @@ -3,7 +3,7 @@ RemoteFortressReader .. dfhack-tool:: :summary: Backend for Armok Vision. - :tags: dev + :tags: dev graphics :no-command: .. dfhack-command:: RemoteFortressReader_version diff --git a/docs/plugins/add-spatter.rst b/docs/plugins/add-spatter.rst index 06739291e..873bd761f 100644 --- a/docs/plugins/add-spatter.rst +++ b/docs/plugins/add-spatter.rst @@ -3,7 +3,7 @@ add-spatter .. dfhack-tool:: :summary: Make tagged reactions produce contaminants. - :tags: adventure fort mod items + :tags: adventure fort gameplay items :no-command: Give some use to all those poisons that can be bought from caravans! The plugin diff --git a/docs/plugins/autochop.rst b/docs/plugins/autochop.rst index b445384fb..f0f7932fd 100644 --- a/docs/plugins/autochop.rst +++ b/docs/plugins/autochop.rst @@ -3,7 +3,7 @@ autochop .. dfhack-tool:: :summary: Auto-harvest trees when low on stockpiled logs. - :tags: fort auto + :tags: fort auto plants :no-command: This plugin can designate trees for chopping when your stocks are low on logs. diff --git a/docs/plugins/autoclothing.rst b/docs/plugins/autoclothing.rst index a5b57dfb1..ed4f2160d 100644 --- a/docs/plugins/autoclothing.rst +++ b/docs/plugins/autoclothing.rst @@ -3,7 +3,7 @@ autoclothing .. dfhack-tool:: :summary: Automatically manage clothing work orders. - :tags: fort auto jobs + :tags: fort auto workorders This command allows you to set how many of each clothing type every citizen should have. diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst index 0fd7ee23c..e2d812219 100644 --- a/docs/plugins/autodump.rst +++ b/docs/plugins/autodump.rst @@ -3,7 +3,7 @@ autodump .. dfhack-tool:: :summary: Automatically set items in a stockpile to be dumped. - :tags: fort auto fps items stockpiles + :tags: fort productivity fps armok stockpiles items :no-command: .. dfhack-command:: autodump diff --git a/docs/plugins/autofarm.rst b/docs/plugins/autofarm.rst index 44077eb89..026a2ad61 100644 --- a/docs/plugins/autofarm.rst +++ b/docs/plugins/autofarm.rst @@ -3,7 +3,7 @@ autofarm .. dfhack-tool:: :summary: Automatically manage farm crop selection. - :tags: fort auto buildings + :tags: fort auto plants Periodically scan your plant stocks and assign crops to your farm plots based on which plant stocks are low (as long as you have the appropriate seeds). The diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index f95d74fea..9efa33499 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -3,7 +3,7 @@ autogems .. dfhack-tool:: :summary: Automatically cut rough gems. - :tags: fort auto jobs + :tags: fort auto workorders :no-command: .. dfhack-command:: autogems-reload diff --git a/docs/plugins/automelt.rst b/docs/plugins/automelt.rst index 508fdaeaf..09b35a378 100644 --- a/docs/plugins/automelt.rst +++ b/docs/plugins/automelt.rst @@ -3,7 +3,7 @@ automelt .. dfhack-tool:: :summary: Quickly designate items to be melted. - :tags: fort auto items stockpiles + :tags: fort productivity stockpiles items :no-command: When `enabled `, this plugin adds an option to the :kbd:`q` menu for diff --git a/docs/plugins/autotrade.rst b/docs/plugins/autotrade.rst index f27fab122..946e53508 100644 --- a/docs/plugins/autotrade.rst +++ b/docs/plugins/autotrade.rst @@ -3,7 +3,7 @@ autotrade .. dfhack-tool:: :summary: Quickly designate items to be traded. - :tags: fort auto items stockpiles + :tags: fort productivity stockpiles items :no-command: When `enabled `, this plugin adds an option to the :kbd:`q` menu for diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst index 8654aa1ec..ed3d0b29c 100644 --- a/docs/plugins/blueprint.rst +++ b/docs/plugins/blueprint.rst @@ -3,7 +3,7 @@ blueprint .. dfhack-tool:: :summary: Record a live game map in a quickfort blueprint. - :tags: fort design quickfort map + :tags: fort design buildings stockpiles map With ``blueprint``, you can export the structure of a portion of your fortress in a blueprint file that you (or anyone else) can later play back with diff --git a/docs/plugins/building-hacks.rst b/docs/plugins/building-hacks.rst index cd8835f37..12f49fbbe 100644 --- a/docs/plugins/building-hacks.rst +++ b/docs/plugins/building-hacks.rst @@ -3,7 +3,7 @@ building-hacks .. dfhack-tool:: :summary: Provides a Lua API for creating powered workshops. - :tags: fort mod buildings + :tags: fort gameplay buildings :no-command: See `building-hacks-api` for more details. diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index ea9d78f64..4e96e3349 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -3,7 +3,7 @@ buildingplan .. dfhack-tool:: :summary: Plan building construction before you have materials. - :tags: fort design quickfort buildings map + :tags: fort design buildings This plugin adds a planning mode for building placement. You can then place furniture, constructions, and other buildings before the required materials are diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst index 92e62f08b..1bdbd959f 100644 --- a/docs/plugins/burrows.rst +++ b/docs/plugins/burrows.rst @@ -3,7 +3,7 @@ burrows .. dfhack-tool:: :summary: Auto-expand burrows as you dig. - :tags: fort auto productivity units + :tags: fort auto productivity design map units :no-command: .. dfhack-command:: burrow diff --git a/docs/plugins/cleanconst.rst b/docs/plugins/cleanconst.rst index 9c0309105..9417bd539 100644 --- a/docs/plugins/cleanconst.rst +++ b/docs/plugins/cleanconst.rst @@ -3,7 +3,7 @@ cleanconst .. dfhack-tool:: :summary: Cleans up construction materials. - :tags: fort fps map + :tags: fort fps buildings This tool alters all constructions on the map so that they spawn their building component when they are disassembled, allowing their actual build items to be diff --git a/docs/plugins/cleaners.rst b/docs/plugins/cleaners.rst index 42e585da1..ca022c440 100644 --- a/docs/plugins/cleaners.rst +++ b/docs/plugins/cleaners.rst @@ -6,7 +6,7 @@ cleaners .. dfhack-tool:: :summary: Provides commands for cleaning spatter from the map. - :tags: adventure fort fps items map units + :tags: adventure fort fps armok items map units :no-command: .. dfhack-command:: clean diff --git a/docs/plugins/command-prompt.rst b/docs/plugins/command-prompt.rst index 278741f36..802a1898e 100644 --- a/docs/plugins/command-prompt.rst +++ b/docs/plugins/command-prompt.rst @@ -3,7 +3,7 @@ command-prompt .. dfhack-tool:: :summary: An in-game DFHack terminal where you can run other commands. - :tags: system + :tags: dfhack Usage:: diff --git a/docs/plugins/cursecheck.rst b/docs/plugins/cursecheck.rst index b2e2943ba..afe6b537f 100644 --- a/docs/plugins/cursecheck.rst +++ b/docs/plugins/cursecheck.rst @@ -3,7 +3,7 @@ cursecheck .. dfhack-tool:: :summary: Check for cursed creatures. - :tags: system interface + :tags: dev fps stockpiles This command checks a single map tile (or the whole map/world) for cursed creatures (ghosts, vampires, necromancers, werebeasts, zombies, etc.). diff --git a/docs/plugins/diggingInvaders.rst b/docs/plugins/diggingInvaders.rst index df54e7008..bdb65b490 100644 --- a/docs/plugins/diggingInvaders.rst +++ b/docs/plugins/diggingInvaders.rst @@ -3,7 +3,7 @@ diggingInvaders .. dfhack-tool:: :summary: Invaders dig and destroy to get to your dwarves. - :tags: fort mod map + :tags: fort gameplay military units Usage: diff --git a/docs/plugins/dwarfmonitor.rst b/docs/plugins/dwarfmonitor.rst index 73d8a8c19..1c499639f 100644 --- a/docs/plugins/dwarfmonitor.rst +++ b/docs/plugins/dwarfmonitor.rst @@ -3,7 +3,7 @@ dwarfmonitor .. dfhack-tool:: :summary: Measure fort happiness and efficiency. - :tags: fort inspection units + :tags: fort inspection jobs units It can also show heads-up display widgets with live fort statistics. diff --git a/docs/plugins/dwarfvet.rst b/docs/plugins/dwarfvet.rst index f607e85cd..fc8f84659 100644 --- a/docs/plugins/dwarfvet.rst +++ b/docs/plugins/dwarfvet.rst @@ -3,7 +3,7 @@ dwarfvet .. dfhack-tool:: :summary: Allows animals to be treated at animal hospitals. - :tags: fort mod animals + :tags: fort gameplay animals Annoyed your dragons become useless after a minor injury? Well, with dwarfvet, injured animals will be treated at an animal hospital, which is simply a hospital diff --git a/docs/plugins/eventful.rst b/docs/plugins/eventful.rst index d5bd55956..e89480413 100644 --- a/docs/plugins/eventful.rst +++ b/docs/plugins/eventful.rst @@ -3,7 +3,7 @@ eventful .. dfhack-tool:: :summary: Provides a Lua API for reacting to in-game events. - :tags: dev mod + :tags: dev gameplay :no-command: See `eventful-api` for details. diff --git a/docs/plugins/fix-unit-occupancy.rst b/docs/plugins/fix-unit-occupancy.rst index 117f97467..ba75ed389 100644 --- a/docs/plugins/fix-unit-occupancy.rst +++ b/docs/plugins/fix-unit-occupancy.rst @@ -3,7 +3,7 @@ fix-unit-occupancy .. dfhack-tool:: :summary: Fix phantom unit occupancy issues. - :tags: fort fix map + :tags: fort bugfix map If you see "unit blocking tile" messages that you can't account for (:bug:`3499`), this tool can help. diff --git a/docs/plugins/fixveins.rst b/docs/plugins/fixveins.rst index 045b3c8da..f8b9c4c36 100644 --- a/docs/plugins/fixveins.rst +++ b/docs/plugins/fixveins.rst @@ -3,7 +3,7 @@ fixveins .. dfhack-tool:: :summary: Restore missing mineral inclusions. - :tags: fort fix map + :tags: fort bugfix map This tool can also remove invalid references to mineral inclusions if you broke your embark with tools like `tiletypes`. diff --git a/docs/plugins/forceequip.rst b/docs/plugins/forceequip.rst index acfd8f462..b835865e2 100644 --- a/docs/plugins/forceequip.rst +++ b/docs/plugins/forceequip.rst @@ -3,7 +3,7 @@ forceequip .. dfhack-tool:: :summary: Move items into a unit's inventory. - :tags: adventure fort items units + :tags: adventure fort animals items military units This tool is typically used to equip specific clothing/armor items onto a dwarf, but can also be used to put armor onto a war animal or to add unusual items diff --git a/docs/plugins/getplants.rst b/docs/plugins/getplants.rst index 502f6e9ae..90f4203b9 100644 --- a/docs/plugins/getplants.rst +++ b/docs/plugins/getplants.rst @@ -3,7 +3,7 @@ getplants .. dfhack-tool:: :summary: Designate trees for chopping and shrubs for gathering. - :tags: fort productivity + :tags: fort productivity plants Specify the types of trees to cut down and/or shrubs to gather by their plant names. diff --git a/docs/plugins/hotkeys.rst b/docs/plugins/hotkeys.rst index 7d30d486e..6115bc90c 100644 --- a/docs/plugins/hotkeys.rst +++ b/docs/plugins/hotkeys.rst @@ -3,7 +3,7 @@ hotkeys .. dfhack-tool:: :summary: Show all dfhack keybindings for the current context. - :tags: system productivity interface + :tags: dfhack The command opens an in-game screen showing which DFHack keybindings are active in the current context. See also `hotkey-notes`. diff --git a/docs/plugins/infiniteSky.rst b/docs/plugins/infiniteSky.rst index 46f30c24c..144c6c6e7 100644 --- a/docs/plugins/infiniteSky.rst +++ b/docs/plugins/infiniteSky.rst @@ -3,7 +3,7 @@ infiniteSky .. dfhack-tool:: :summary: Automatically allocates new z-levels of sky - :tags: fort map + :tags: fort design map If enabled, this plugin will automatically allocate new z-levels of sky at the top of the map as you build up. Or it can allocate one or many additional levels diff --git a/docs/plugins/isoworldremote.rst b/docs/plugins/isoworldremote.rst index 1fa439b29..284794f96 100644 --- a/docs/plugins/isoworldremote.rst +++ b/docs/plugins/isoworldremote.rst @@ -3,7 +3,7 @@ isoworldremote .. dfhack-tool:: :summary: Provides a remote API used by Isoworld. - :tags: dev mod + :tags: dev graphics :no-command: See `remote` for related remote APIs. diff --git a/docs/plugins/luasocket.rst b/docs/plugins/luasocket.rst index 24b30c7a6..4b5b18540 100644 --- a/docs/plugins/luasocket.rst +++ b/docs/plugins/luasocket.rst @@ -3,7 +3,7 @@ luasocket .. dfhack-tool:: :summary: Provides a Lua API for accessing network sockets. - :tags: dev mod + :tags: dev :no-command: See `luasocket-api` for details. diff --git a/docs/plugins/manipulator.rst b/docs/plugins/manipulator.rst index 26223587d..a0c8ec38e 100644 --- a/docs/plugins/manipulator.rst +++ b/docs/plugins/manipulator.rst @@ -3,7 +3,7 @@ manipulator .. dfhack-tool:: :summary: An in-game labor management interface. - :tags: fort productivity interface labors + :tags: fort productivity labors :no-command: It is equivalent to the popular Dwarf Therapist utility. diff --git a/docs/plugins/map-render.rst b/docs/plugins/map-render.rst index 7754af8b7..efe3ed0c6 100644 --- a/docs/plugins/map-render.rst +++ b/docs/plugins/map-render.rst @@ -3,7 +3,7 @@ map-render .. dfhack-tool:: :summary: Provides a Lua API for rerendering portions of the map. - :tags: dev + :tags: dev graphics :no-command: See `map-render-api` for details. diff --git a/docs/plugins/mode.rst b/docs/plugins/mode.rst index db94e6bac..94ee7cb44 100644 --- a/docs/plugins/mode.rst +++ b/docs/plugins/mode.rst @@ -3,7 +3,7 @@ mode .. dfhack-tool:: :summary: See and change the game mode. - :tags: dev + :tags: dev gameplay armok .. warning:: diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index 9403c6c8e..036e0df11 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -3,7 +3,7 @@ orders .. dfhack-tool:: :summary: Manage manager orders. - :tags: fort productivity jobs + :tags: fort productivity workorders Usage: diff --git a/docs/plugins/pathable.rst b/docs/plugins/pathable.rst index 800580017..f4b89683b 100644 --- a/docs/plugins/pathable.rst +++ b/docs/plugins/pathable.rst @@ -3,7 +3,7 @@ pathable .. dfhack-tool:: :summary: Marks tiles that are reachable from the cursor. - :tags: dev inspection interface map + :tags: dev inspection map :no-command: This plugin provides a Lua API, but no direct commands. See `pathable-api` for diff --git a/docs/plugins/petcapRemover.rst b/docs/plugins/petcapRemover.rst index f3d011308..d4e1f2279 100644 --- a/docs/plugins/petcapRemover.rst +++ b/docs/plugins/petcapRemover.rst @@ -3,7 +3,7 @@ petcapRemover .. dfhack-tool:: :summary: Modify the pet population cap. - :tags: fort armok animals + :tags: fort animals In vanilla DF, pets will not reproduce unless the population is below 50 and the number of children of that species is below a certain percentage. This plugin diff --git a/docs/plugins/plants.rst b/docs/plugins/plants.rst index d28119af9..cc23c84dd 100644 --- a/docs/plugins/plants.rst +++ b/docs/plugins/plants.rst @@ -5,7 +5,7 @@ plants .. dfhack-tool:: :summary: Provides commands that interact with plants. - :tags: adventure fort armok map + :tags: adventure fort armok map plants :no-command: .. dfhack-command:: plant diff --git a/docs/plugins/power-meter.rst b/docs/plugins/power-meter.rst index 01e75491a..5e608ca46 100644 --- a/docs/plugins/power-meter.rst +++ b/docs/plugins/power-meter.rst @@ -3,7 +3,7 @@ power-meter .. dfhack-tool:: :summary: Allow presure plates to measure power. - :tags: fort mod buildings + :tags: fort gameplay buildings :no-command: If you run `gui/power-meter` while building a pressure plate, the pressure diff --git a/docs/plugins/probe.rst b/docs/plugins/probe.rst index 54436be68..7da332ba5 100644 --- a/docs/plugins/probe.rst +++ b/docs/plugins/probe.rst @@ -3,7 +3,7 @@ probe .. dfhack-tool:: :summary: Display low-level properties of the selected tile. - :tags: adventure fort inspection map + :tags: adventure fort inspection buildings map units .. dfhack-command:: bprobe :summary: Display low-level properties of the selected building. diff --git a/docs/plugins/prospector.rst b/docs/plugins/prospector.rst index dc6204c06..7ef3d223c 100644 --- a/docs/plugins/prospector.rst +++ b/docs/plugins/prospector.rst @@ -5,7 +5,7 @@ prospector .. dfhack-tool:: :summary: Provides commands that help you analyze natural resources. - :tags: fort embark inspection map + :tags: fort embark inspection armok map :no-command: .. dfhack-command:: prospect diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst index 72f077fcd..a4b011a1e 100644 --- a/docs/plugins/regrass.rst +++ b/docs/plugins/regrass.rst @@ -3,7 +3,7 @@ regrass .. dfhack-tool:: :summary: Regrows all the grass. - :tags: adventure fort armok animals + :tags: adventure fort armok animals map Use this command if your grazers have eaten everything down to the dirt. diff --git a/docs/plugins/rename.rst b/docs/plugins/rename.rst index 02bc04c2c..c3e4d5b59 100644 --- a/docs/plugins/rename.rst +++ b/docs/plugins/rename.rst @@ -3,7 +3,7 @@ rename .. dfhack-tool:: :summary: Easily rename things. - :tags: adventure fort productivity + :tags: adventure fort productivity buildings stockpiles units Use `gui/rename` for an in-game interface. diff --git a/docs/plugins/rendermax.rst b/docs/plugins/rendermax.rst index 680676fa3..af3dc6c0c 100644 --- a/docs/plugins/rendermax.rst +++ b/docs/plugins/rendermax.rst @@ -3,7 +3,7 @@ rendermax .. dfhack-tool:: :summary: Modify the map lighting. - :tags: adventure fort mod + :tags: adventure fort gameplay graphics This plugin provides a collection of OpenGL lighting filters that affect how the map is drawn to the screen. diff --git a/docs/plugins/resume.rst b/docs/plugins/resume.rst index 0713026e4..b8c388738 100644 --- a/docs/plugins/resume.rst +++ b/docs/plugins/resume.rst @@ -3,7 +3,7 @@ resume .. dfhack-tool:: :summary: Color planned buildings based on their suspend status. - :tags: fort productivity + :tags: fort productivity interface jobs :no-command: .. dfhack-command:: resume diff --git a/docs/plugins/seedwatch.rst b/docs/plugins/seedwatch.rst index 1aaa04ee3..7d31c160c 100644 --- a/docs/plugins/seedwatch.rst +++ b/docs/plugins/seedwatch.rst @@ -3,7 +3,7 @@ seedwatch .. dfhack-tool:: :summary: Manages seed and plant cooking based on seed stock levels. - :tags: fort auto + :tags: fort auto plants Each seed type can be assigned a target. If the number of seeds of that type falls below that target, then the plants and seeds of that type will be excluded diff --git a/docs/plugins/showmood.rst b/docs/plugins/showmood.rst index da67b5803..2a294b166 100644 --- a/docs/plugins/showmood.rst +++ b/docs/plugins/showmood.rst @@ -3,7 +3,7 @@ showmood .. dfhack-tool:: :summary: Shows all items needed for the active strange mood. - :tags: fort inspection jobs + :tags: fort inspection armok jobs units Usage:: diff --git a/docs/plugins/siege-engine.rst b/docs/plugins/siege-engine.rst index 06ff963b7..a6b3e87a8 100644 --- a/docs/plugins/siege-engine.rst +++ b/docs/plugins/siege-engine.rst @@ -3,7 +3,7 @@ siege-engine .. dfhack-tool:: :summary: Extend the functionality and usability of siege engines. - :tags: fort mod buildings + :tags: fort gameplay buildings :no-command: Siege engines in DF haven't been updated since the game was 2D, and can only aim diff --git a/docs/plugins/spectate.rst b/docs/plugins/spectate.rst index 7f448ebfb..96147695a 100644 --- a/docs/plugins/spectate.rst +++ b/docs/plugins/spectate.rst @@ -3,7 +3,7 @@ spectate .. dfhack-tool:: :summary: Automatically follow exciting dwarves. - :tags: fort units + :tags: fort interface :no-command: Usage:: diff --git a/docs/plugins/steam-engine.rst b/docs/plugins/steam-engine.rst index e0f938915..6560ae714 100644 --- a/docs/plugins/steam-engine.rst +++ b/docs/plugins/steam-engine.rst @@ -3,7 +3,7 @@ steam-engine .. dfhack-tool:: :summary: Allow modded steam engine buildings to function. - :tags: fort mod buildings + :tags: fort gameplay buildings :no-command: The steam-engine plugin detects custom workshops with the string diff --git a/docs/plugins/stockflow.rst b/docs/plugins/stockflow.rst index 81eab07ff..358305a96 100644 --- a/docs/plugins/stockflow.rst +++ b/docs/plugins/stockflow.rst @@ -3,7 +3,7 @@ stockflow .. dfhack-tool:: :summary: Queue manager jobs based on free space in stockpiles. - :tags: fort auto jobs stockpiles + :tags: fort auto stockpiles workorders With this plugin, the fortress bookkeeper can tally up free space in specific stockpiles and queue jobs through the manager to produce items to fill the free diff --git a/docs/plugins/stocks.rst b/docs/plugins/stocks.rst index e518fc1f1..144ba2287 100644 --- a/docs/plugins/stocks.rst +++ b/docs/plugins/stocks.rst @@ -3,7 +3,7 @@ stocks .. dfhack-tool:: :summary: Enhanced fortress stock management interface. - :tags: fort productivity interface + :tags: fort productivity items When the plugin is enabled, two new hotkeys become available: diff --git a/docs/plugins/stonesense.rst b/docs/plugins/stonesense.rst index 0315c92ed..b43f95697 100644 --- a/docs/plugins/stonesense.rst +++ b/docs/plugins/stonesense.rst @@ -3,7 +3,7 @@ stonesense .. dfhack-tool:: :summary: A 3D isometric visualizer. - :tags: adventure fort interface map + :tags: adventure fort map graphics .. dfhack-command:: ssense :summary: An alias for stonesense. diff --git a/docs/plugins/tailor.rst b/docs/plugins/tailor.rst index 5da293fbf..646e73f91 100644 --- a/docs/plugins/tailor.rst +++ b/docs/plugins/tailor.rst @@ -3,7 +3,7 @@ tailor .. dfhack-tool:: :summary: Automatically keep your dwarves in fresh clothing. - :tags: fort auto jobs + :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 diff --git a/docs/plugins/title-folder.rst b/docs/plugins/title-folder.rst index 56de7d769..ca349a0ed 100644 --- a/docs/plugins/title-folder.rst +++ b/docs/plugins/title-folder.rst @@ -3,7 +3,7 @@ title-folder .. dfhack-tool:: :summary: Displays the DF folder name in the window title bar. - :tags: system interface + :tags: interface :no-command: Usage:: diff --git a/docs/plugins/title-version.rst b/docs/plugins/title-version.rst index f7fc238c7..239b08bc2 100644 --- a/docs/plugins/title-version.rst +++ b/docs/plugins/title-version.rst @@ -3,7 +3,7 @@ title-version .. dfhack-tool:: :summary: Displays the DFHack version on DF's title screen. - :tags: system interface + :tags: interface :no-command: Usage:: diff --git a/docs/plugins/trackstop.rst b/docs/plugins/trackstop.rst index 65aae2b7f..1c638dfd7 100644 --- a/docs/plugins/trackstop.rst +++ b/docs/plugins/trackstop.rst @@ -3,7 +3,7 @@ trackstop .. dfhack-tool:: :summary: Add dynamic configuration options for track stops. - :tags: fort interface mod buildings + :tags: fort gameplay buildings :no-command: When enabled, this plugin adds a :kbd:`q` menu for track stops, which is diff --git a/docs/plugins/tweak.rst b/docs/plugins/tweak.rst index 71b564434..c04392c62 100644 --- a/docs/plugins/tweak.rst +++ b/docs/plugins/tweak.rst @@ -3,7 +3,7 @@ tweak .. dfhack-tool:: :summary: A collection of tweaks and bugfixes. - :tags: adventure fort interface fps fix armok + :tags: adventure fort fps bugfix armok interface Usage:: diff --git a/docs/plugins/workNow.rst b/docs/plugins/workNow.rst index 678649487..27709c166 100644 --- a/docs/plugins/workNow.rst +++ b/docs/plugins/workNow.rst @@ -3,7 +3,7 @@ workNow .. dfhack-tool:: :summary: Reduce the time that dwarves idle after completing a job. - :tags: fort auto jobs + :tags: fort auto labors After finishing a job, dwarves will wander away for a while before picking up a new job. This plugin will automatically poke the game to assign dwarves to new From 2a3a812b3c7c5de15dfb7510c2a998483056d3cc Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 13 Aug 2022 21:21:48 -0700 Subject: [PATCH 483/854] update tag reference in Core.rst --- docs/Core.rst | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Core.rst b/docs/Core.rst index 025e951e6..09ae2577d 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -185,7 +185,7 @@ where ``*`` can be any string, including the empty string. A world being loaded can mean a fortress, an adventurer, or legends mode. These files are best used for non-persistent commands, such as setting -a `tag/fix` script to run on `repeat`. +a `tag/bugfix` script to run on `repeat`. .. _onMapLoad.init: diff --git a/scripts b/scripts index 4c7eecb5c..f37c0d022 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 4c7eecb5cbeeead3c4374fafc562ee774e8e229e +Subproject commit f37c0d022135ca10fb5375c4e56e11215bf208ae From 47dc8c6c11d4dd121c5dcf8542125d08a24f2dcd Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 13 Aug 2022 21:23:26 -0700 Subject: [PATCH 484/854] update scripts reference --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index f37c0d022..3d65c9a75 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit f37c0d022135ca10fb5375c4e56e11215bf208ae +Subproject commit 3d65c9a75a8c7a3607e71529f6264bab3e637755 From f1f207b45b57b28cdf0ce5f53456423f28f385ba Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 13 Aug 2022 21:51:36 -0700 Subject: [PATCH 485/854] don't include rst sources in html output --- CMakeLists.txt | 4 +++- conf.py | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c341735c9..c02b3a670 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -506,7 +506,9 @@ if(BUILD_DOCS) COMMAND ${CMAKE_COMMAND} -E touch ${SPHINX_OUTPUT}) install(DIRECTORY ${dfhack_SOURCE_DIR}/docs/html/ - DESTINATION ${DFHACK_USERDOC_DESTINATION}/docs) + DESTINATION ${DFHACK_USERDOC_DESTINATION}/docs + FILES_MATCHING PATTERN "*" + PATTERN html/_sources EXCLUDE) install(DIRECTORY ${dfhack_SOURCE_DIR}/docs/text/ DESTINATION ${DFHACK_USERDOC_DESTINATION}/docs) install(FILES docs/changelogs/news.rst docs/changelogs/news-dev.rst DESTINATION ${DFHACK_USERDOC_DESTINATION}) diff --git a/conf.py b/conf.py index ff4cdc59d..0eaeb95f7 100644 --- a/conf.py +++ b/conf.py @@ -316,6 +316,9 @@ html_domain_indices = False # If false, no genindex.html is generated. html_use_index = True +# don't link to rst sources in the generated pages +html_show_sourcelink = False + html_css_files = [ 'dfhack.css', ] From 09401ac3707ad6348b2287f8a9464ee06ea8986d Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sun, 14 Aug 2022 07:16:48 +0000 Subject: [PATCH 486/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 4c7eecb5c..3d65c9a75 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 4c7eecb5cbeeead3c4374fafc562ee774e8e229e +Subproject commit 3d65c9a75a8c7a3607e71529f6264bab3e637755 From 488fd677429310a684a5ac1d1e1d0eef98a671cf Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 14 Aug 2022 23:01:20 -0700 Subject: [PATCH 487/854] output version of found sphinx and python --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c02b3a670..c278c4542 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -442,8 +442,10 @@ endif() add_subdirectory(data) add_subdirectory(scripts) -find_package(Sphinx QUIET) if(BUILD_DOCS) + find_package(Python3) + find_package(Sphinx) + if(NOT SPHINX_FOUND) message(SEND_ERROR "Sphinx not found but BUILD_DOCS enabled") endif() From 340b524348eabc0fa750232305000586e5b8646f Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 15 Aug 2022 17:49:44 -0400 Subject: [PATCH 488/854] Invoke build.py with cmake-found python In the Buildmaster GCC 4.8 image, `/usr/bin/env python3` appears to find the system Python (3.4) as opposed to the newer Python (3.6) we install separately. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c278c4542..60bffa758 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -493,7 +493,7 @@ if(BUILD_DOCS) "${CMAKE_BINARY_DIR}/docs/text" ) add_custom_command(OUTPUT ${SPHINX_OUTPUT} - COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/docs/build.py" + COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/docs/build.py" html text --sphinx="${SPHINX_EXECUTABLE}" -- -q DEPENDS ${SPHINX_DEPS} COMMENT "Building documentation with Sphinx" From 9f648d532ee29897dbf3acd5bfafb4d15b7932fd Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 14:22:21 -0700 Subject: [PATCH 489/854] modify seedwatch all to actually watch all seeds --- docs/changelog.txt | 1 + plugins/seedwatch.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 2733cd539..637dd03f0 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -51,6 +51,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. - `orders`: added useful library of manager orders. see them with ``orders list`` and import them with, for example, ``orders import library/basic`` - `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. +- `seedwatch`: ``seedwatch all`` now adds all plants with seeds to the watchlist, not just the "basic" crops. ## Documentation diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index 478a0899a..65b89cfa1 100644 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -132,7 +132,9 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) map plantIDs; for(size_t i = 0; i < world->raws.plants.all.size(); ++i) { - plantIDs[world->raws.plants.all[i]->id] = i; + auto & plant = world->raws.plants.all[i]; + if (plant->material_defs.type[plant_material_def::seed] != -1) + plantIDs[plant->id] = i; } t_gamemodes gm; @@ -226,10 +228,8 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) if(limit < 0) limit = 0; if(parameters[0] == "all") { - for(auto i = abbreviations.begin(); i != abbreviations.end(); ++i) - { - if(plantIDs.count(i->second) > 0) Kitchen::setLimit(plantIDs[i->second], limit); - } + for(auto & entry : plantIDs) + Kitchen::setLimit(entry.second, limit); } else { From 2f9021a3a0911d1cd39108535e02b91f4abb8db3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 12:56:44 -0700 Subject: [PATCH 490/854] move examples to the examples folder --- docs/Dev-intro.rst | 4 +-- plugins/CMakeLists.txt | 6 ---- plugins/{skeleton => examples}/skeleton.cpp | 0 .../{skeleton => examples}/skeletonShort.cpp | 0 plugins/skeleton/CMakeLists.txt | 36 ------------------- plugins/skeleton/skeleton.h | 1 - 6 files changed, 2 insertions(+), 45 deletions(-) rename plugins/{skeleton => examples}/skeleton.cpp (100%) rename plugins/{skeleton => examples}/skeletonShort.cpp (100%) delete mode 100644 plugins/skeleton/CMakeLists.txt delete mode 100644 plugins/skeleton/skeleton.h diff --git a/docs/Dev-intro.rst b/docs/Dev-intro.rst index 758bf225f..d4167aae3 100644 --- a/docs/Dev-intro.rst +++ b/docs/Dev-intro.rst @@ -22,7 +22,7 @@ Plugins DFHack plugins are written in C++ and located in the ``plugins`` folder. Currently, documentation on how to write plugins is somewhat sparse. There are -templates that you can use to get started in the ``plugins/skeleton`` +templates that you can use to get started in the ``plugins/examples`` folder, and the source code of existing plugins can also be helpful. If you want to compile a plugin that you have just added, you will need to add a @@ -35,7 +35,7 @@ other commands). Plugins can also register handlers to run on every tick, and can interface with the built-in `enable` and `disable` commands. For the full plugin API, see the -skeleton plugins or ``PluginManager.cpp``. +example plugins or ``PluginManager.cpp``. Installed plugins live in the ``hack/plugins`` folder of a DFHack installation, and the `load` family of commands can be used to load a recompiled plugin diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 338db4ab9..6a34117b9 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -185,12 +185,6 @@ if(BUILD_SUPPORTED) # see instructions for adding "external" plugins at the end of this file. endif() -# this is the skeleton plugin. If you want to make your own, make a copy and then change it -option(BUILD_SKELETON "Build the skeleton plugin." OFF) -if(BUILD_SKELETON) - add_subdirectory(skeleton) -endif() - macro(subdirlist result subdir) file(GLOB children ABSOLUTE ${subdir}/ ${subdir}/*/) set(dirlist "") diff --git a/plugins/skeleton/skeleton.cpp b/plugins/examples/skeleton.cpp similarity index 100% rename from plugins/skeleton/skeleton.cpp rename to plugins/examples/skeleton.cpp diff --git a/plugins/skeleton/skeletonShort.cpp b/plugins/examples/skeletonShort.cpp similarity index 100% rename from plugins/skeleton/skeletonShort.cpp rename to plugins/examples/skeletonShort.cpp diff --git a/plugins/skeleton/CMakeLists.txt b/plugins/skeleton/CMakeLists.txt deleted file mode 100644 index cbe5f7ce6..000000000 --- a/plugins/skeleton/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -project(skeleton) -# A list of source files -set(PROJECT_SRCS - skeleton.cpp -) -# A list of headers -set(PROJECT_HDRS - skeleton.h -) -set_source_files_properties(${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) - -# mash them together (headers are marked as headers and nothing will try to compile them) -list(APPEND PROJECT_SRCS ${PROJECT_HDRS}) - -# option to use a thread for no particular reason -option(SKELETON_THREAD "Use threads in the skeleton plugin." ON) -if(UNIX) - if(APPLE) - set(PROJECT_LIBS - # add any extra mac libraries here - ${PROJECT_LIBS} - ) - else() - set(PROJECT_LIBS - # add any extra linux libraries here - ${PROJECT_LIBS} - ) - endif() -else() - set(PROJECT_LIBS - # add any extra windows libraries here - ${PROJECT_LIBS} - ) -endif() -# this makes sure all the stuff is put in proper places and linked to dfhack -dfhack_plugin(skeleton ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) diff --git a/plugins/skeleton/skeleton.h b/plugins/skeleton/skeleton.h deleted file mode 100644 index 6f70f09be..000000000 --- a/plugins/skeleton/skeleton.h +++ /dev/null @@ -1 +0,0 @@ -#pragma once From 0bbbacf161aa0f4bd4869d108f56d85f9c5ec8e6 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 18:40:50 -0700 Subject: [PATCH 491/854] extend the docs and examples in skeleton.cpp --- plugins/examples/skeleton.cpp | 221 +++++++++++++++++++++------------- 1 file changed, 134 insertions(+), 87 deletions(-) diff --git a/plugins/examples/skeleton.cpp b/plugins/examples/skeleton.cpp index 7d5936f6d..e1f552d31 100644 --- a/plugins/examples/skeleton.cpp +++ b/plugins/examples/skeleton.cpp @@ -1,65 +1,71 @@ -// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D +// This is an example plugin that just documents and implements all the plugin +// callbacks and features. You can compile it, load it, run it, and see the +// debug messages get printed to the console. +// +// See the other example plugins in this directory for plugins that are +// configured for specific use cases (but don't come with as many comments as +// this one does). + +#include +#include + +#include "df/world.h" -// some headers required for a plugin. Nothing special, just the basics. #include "Core.h" -#include -#include -#include -#include -// If you need to save data per-world: -//#include "modules/Persistence.h" +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" -// DF data structure definition headers -#include "DataDefs.h" -//#include "df/world.h" +#include "modules/Persistence.h" +#include "modules/World.h" -// our own, empty header. -#include "skeleton.h" +using std::string; +using std::vector; using namespace DFHack; -using namespace df::enums; -// Expose the plugin name to the DFHack core, as well as metadata like the DFHack version. -// The name string provided must correspond to the filename - +// Expose the plugin name to the DFHack core, as well as metadata like the +// DFHack version that this plugin was compiled with. This macro provides a +// variable for the plugin name as const char * plugin_name. +// The name provided must correspond to the filename -- // skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case DFHACK_PLUGIN("skeleton"); -// The identifier declared with this macro (ie. enabled) can be specified by the user -// and subsequently used to manage the plugin's operations. -// This will also be tracked by `plug`; when true the plugin will be shown as enabled. -DFHACK_PLUGIN_IS_ENABLED(enabled); +// The identifier declared with this macro (i.e. is_enabled) is used to track +// whether the plugin is in an "enabled" state. If you don't need enablement +// for your plugin, you don't need this line. This variable will also be read +// by the `plug` builtin command; when true the plugin will be shown as enabled. +DFHACK_PLUGIN_IS_ENABLED(is_enabled); // Any globals a plugin requires (e.g. world) should be listed here. // For example, this line expands to "using df::global::world" and prevents the -// plugin from being loaded if df::global::world is null (i.e. missing from symbols.xml): -// +// plugin from being loaded if df::global::world is null (i.e. missing from +// symbols.xml). REQUIRE_GLOBAL(world); -// You may want some compile time debugging options -// one easy system just requires you to cache the color_ostream &out into a global debug variable -//#define P_DEBUG 1 -//uint16_t maxTickFreq = 1200; //maybe you want to use some events +// logging levels can be dynamically controlled with the `debugfilter` command. +namespace DFHack { + // for configuration-related logging + DBG_DECLARE(skeleton, status, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(skeleton, cycle, DebugCategory::LINFO); +} -command_result command_callback1(color_ostream &out, std::vector ¶meters); +command_result command_callback1(color_ostream &out, vector ¶meters); +// run when the plugin is loaded DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("skeleton", - "~54 character description of plugin", //to use one line in the ``[DFHack]# ls`` output - command_callback1, - false, - "example usage" - " skeleton

this may not play nice with the pdf builder.. --- docs/sphinx_extensions/dfhack/tool_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 412a332b8..fef6ca15a 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -265,7 +265,7 @@ def init_tag_indices(app): tag, desc = tag_tuple[0], tag_tuple[1] topidx.write(('- `{name} <{name}-tag-index>`\n').format(name=tag)) topidx.write((' {desc}\n').format(desc=desc)) - register_index(app, tag, '"%s" tag index' % tag) + register_index(app, tag, '%s

%s

' % (tag, desc)) def register(app): From 52011bde7bef90ac550070a1dbe01378d4950b6e Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 23 Sep 2022 11:13:49 -0700 Subject: [PATCH 705/854] share tag list between tool and commands so all relevant tag index entries get generated --- docs/sphinx_extensions/dfhack/tool_docs.py | 29 ++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index fef6ca15a..e9aa6e888 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -125,9 +125,13 @@ class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): else: return parts[-1] - def add_index_entry(self, name, tag) -> None: - indexdata = (name, self.options.get('summary', ''), '', self.env.docname, '', 0) - self.env.domaindata[tag]['objects'].append(indexdata) + def add_index_entries(self, name) -> None: + docname = self.env.docname + tags = self.env.domaindata['tag-repo']['doctags'][docname] + indexdata = (name, self.options.get('summary', ''), '', docname, '', 0) + self.env.domaindata['all']['objects'].append(indexdata) + for tag in tags: + self.env.domaindata[tag]['objects'].append(indexdata) @staticmethod def wrap_box(*children: List[nodes.Node]) -> nodes.Admonition: @@ -154,7 +158,9 @@ class DFHackToolDirective(DFHackToolDirectiveBase): def render_content(self) -> List[nodes.Node]: tag_paragraph = self.make_labeled_paragraph('Tags') - for tag in self.options.get('tags', []): + tags = self.options.get('tags', []) + self.env.domaindata['tag-repo']['doctags'][self.env.docname] = tags + for tag in tags: tag_paragraph += [ addnodes.pending_xref(tag, nodes.inline(text=tag), **{ 'reftype': 'ref', @@ -165,12 +171,11 @@ class DFHackToolDirective(DFHackToolDirectiveBase): }), nodes.inline(text=' | '), ] - self.add_index_entry(self.get_name_or_docname(), tag) tag_paragraph.pop() ret_nodes = [tag_paragraph] if 'no-command' in self.options: - self.add_index_entry(self.get_name_or_docname() + ' (plugin)', 'all') + self.add_index_entries(self.get_name_or_docname() + ' (plugin)') ret_nodes += [make_summary(self.env.app.builder, self.options.get('summary', ''))] return ret_nodes @@ -188,7 +193,7 @@ class DFHackCommandDirective(DFHackToolDirectiveBase): def render_content(self) -> List[nodes.Node]: command = self.get_name_or_docname() - self.add_index_entry(command, 'all') + self.add_index_entries(command) return [ self.make_labeled_paragraph('Command', command, content_class=nodes.literal), make_summary(self.env.app.builder, self.options.get('summary', '')), @@ -196,6 +201,15 @@ class DFHackCommandDirective(DFHackToolDirectiveBase): ] +class TagRepoDomain(Domain): + name = 'tag-repo' + label = 'Holds tag associations per document' + initial_data = {'doctags': {}} + + def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: + self.data['doctags'].update(otherdata['doctags']) + + def get_tags(): groups = {} group_re = re.compile(r'"([^"]+)"') @@ -277,6 +291,7 @@ def register(app): def setup(app): app.connect('builder-inited', register) + app.add_domain(TagRepoDomain) register_index(app, 'all', 'Index of DFHack tools') init_tag_indices(app) From 1cd5e8657a759e64e98b7e4348a0e753686a2419 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 23 Sep 2022 11:33:14 -0700 Subject: [PATCH 706/854] link directly to the tool page title instead of the top of the page this is especially important on mobile where the top of the page is taken up with the sidebar boilerplate --- docs/sphinx_extensions/dfhack/tool_docs.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index e9aa6e888..e7302c3cd 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -115,20 +115,23 @@ class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): required_arguments = 0 optional_arguments = 1 + def get_tool_name_from_docname(self): + parts = self.env.docname.split('/') + if 'tools' in parts: + return '/'.join(parts[parts.index('tools') + 1:]) + else: + return parts[-1] + def get_name_or_docname(self): if self.arguments: return self.arguments[0] - else: - parts = self.env.docname.split('/') - if 'tools' in parts: - return '/'.join(parts[parts.index('tools') + 1:]) - else: - return parts[-1] + return self.get_tool_name_from_docname() def add_index_entries(self, name) -> None: docname = self.env.docname + anchor = self.get_tool_name_from_docname().replace('/', '-') tags = self.env.domaindata['tag-repo']['doctags'][docname] - indexdata = (name, self.options.get('summary', ''), '', docname, '', 0) + indexdata = (name, self.options.get('summary', ''), '', docname, anchor, 0) self.env.domaindata['all']['objects'].append(indexdata) for tag in tags: self.env.domaindata[tag]['objects'].append(indexdata) @@ -247,10 +250,10 @@ def tag_domain_merge_domaindata(self, docnames: List[str], otherdata: Dict) -> N def tag_index_generate(self, docnames: Optional[Iterable[str]] = None) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]: content = defaultdict(list) - for name, desc, _, docname, _, _ in self.domain.data['objects']: + for name, desc, _, docname, anchor, _ in self.domain.data['objects']: first_letter = name[0].lower() content[first_letter].append( - IndexEntry(name, 0, docname, '', '', '', desc)) + IndexEntry(name, 0, docname, anchor, '', '', desc)) return (sorted(content.items()), False) def register_index(app, tag, title): From 98b6ad4954ef2b51782488b5744020d449185c50 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 23 Sep 2022 12:34:50 -0700 Subject: [PATCH 707/854] fix index titles on pdf --- docs/Tags.rst | 54 +++++++++++----------- docs/sphinx_extensions/dfhack/tool_docs.py | 16 +++++-- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/docs/Tags.rst b/docs/Tags.rst index 14a12f411..45bfeb9b3 100644 --- a/docs/Tags.rst +++ b/docs/Tags.rst @@ -13,36 +13,36 @@ for the tag assignment spreadsheet. "when" tags ----------- -- `adventure-tag-index`: Tools that are useful while in adventure mode. Note that some tools only tagged with "fort" might also work in adventure mode, but not always in expected ways. Feel free to experiment, though! -- `dfhack-tag-index`: Tools that you use to run DFHack commands or interact with the DFHack library. This tag also includes tools that help you manage the DF game itself (e.g. settings, saving, etc.) -- `embark-tag-index`: Tools that are useful while on the fort embark screen or while creating an adventurer. -- `fort-tag-index`: Tools that are useful while in fort mode. -- `legends-tag-index`: Tools that are useful while in legends mode. +- `adventure `: Tools that are useful while in adventure mode. Note that some tools only tagged with "fort" might also work in adventure mode, but not always in expected ways. Feel free to experiment, though! +- `dfhack `: Tools that you use to run DFHack commands or interact with the DFHack library. This tag also includes tools that help you manage the DF game itself (e.g. settings, saving, etc.) +- `embark `: Tools that are useful while on the fort embark screen or while creating an adventurer. +- `fort `: Tools that are useful while in fort mode. +- `legends `: Tools that are useful while in legends mode. "why" tags ---------- -- `armok-tag-index`: Tools that give you complete control over an aspect of the game or provide access to information that the game intentionally keeps hidden. -- `auto-tag-index`: Tools that run in the background and automatically manage routine, toilsome aspects of your fortress. -- `bugfix-tag-index`: Tools that fix specific bugs, either permanently or on-demand. -- `design-tag-index`: Tools that help you design your fort. -- `dev-tag-index`: Tools that are useful when developing scripts or mods. -- `fps-tag-index`: Tools that help you manage FPS drop. -- `gameplay-tag-index`: Tools that introduce new gameplay elements. -- `inspection-tag-index`: Tools that let you view information that is otherwise difficult to find. -- `productivity-tag-index`: Tools that help you do things that you could do manually, but using the tool is better and faster. +- `armok `: Tools that give you complete control over an aspect of the game or provide access to information that the game intentionally keeps hidden. +- `auto `: Tools that run in the background and automatically manage routine, toilsome aspects of your fortress. +- `bugfix `: Tools that fix specific bugs, either permanently or on-demand. +- `design `: Tools that help you design your fort. +- `dev `: Tools that are useful when developing scripts or mods. +- `fps `: Tools that help you manage FPS drop. +- `gameplay `: Tools that introduce new gameplay elements. +- `inspection `: Tools that let you view information that is otherwise difficult to find. +- `productivity `: Tools that help you do things that you could do manually, but using the tool is better and faster. "what" tags ----------- -- `animals-tag-index`: Tools that interact with animals. -- `buildings-tag-index`: Tools that interact with buildings and furniture. -- `graphics-tag-index`: Tools that interact with game graphics. -- `interface-tag-index`: Tools that interact with or extend the DF user interface. -- `items-tag-index`: Tools that interact with in-game items. -- `jobs-tag-index`: Tools that interact with jobs. -- `labors-tag-index`: Tools that deal with labor assignment. -- `map-tag-index`: Tools that interact with the game map. -- `military-tag-index`: Tools that interact with the military. -- `plants-tag-index`: Tools that interact with trees, shrubs, and crops. -- `stockpiles-tag-index`: Tools that interact wtih stockpiles. -- `units-tag-index`: Tools that interact with units. -- `workorders-tag-index`: Tools that interact with workorders. +- `animals `: Tools that interact with animals. +- `buildings `: Tools that interact with buildings and furniture. +- `graphics `: Tools that interact with game graphics. +- `interface `: Tools that interact with or extend the DF user interface. +- `items `: Tools that interact with in-game items. +- `jobs `: Tools that interact with jobs. +- `labors `: Tools that deal with labor assignment. +- `map `: Tools that interact with the game map. +- `military `: Tools that interact with the military. +- `plants `: Tools that interact with trees, shrubs, and crops. +- `stockpiles `: Tools that interact wtih stockpiles. +- `units `: Tools that interact with units. +- `workorders `: Tools that interact with workorders. diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index e7302c3cd..581cee26e 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -216,7 +216,7 @@ class TagRepoDomain(Domain): def get_tags(): groups = {} group_re = re.compile(r'"([^"]+)"') - tag_re = re.compile(r'- `([^`]+)-tag-index`: (.*)') + tag_re = re.compile(r'- `([^ ]+) <[^>]+>`: (.*)') with open('docs/Tags.rst') as f: lines = f.readlines() for line in lines: @@ -282,15 +282,25 @@ def init_tag_indices(app): tag, desc = tag_tuple[0], tag_tuple[1] topidx.write(('- `{name} <{name}-tag-index>`\n').format(name=tag)) topidx.write((' {desc}\n').format(desc=desc)) - register_index(app, tag, '%s

%s

' % (tag, desc)) + register_index(app, tag, desc) +def update_index_titles(app): + for domain in app.env.domains.values(): + for index in domain.indices: + if index.shortname == 'all': + continue + if app.builder.format == 'html': + index.localname = '"%s" tag index

%s

' % (index.shortname, index.localname) + else: + index.localname = '%s tag index - %s' % (index.shortname, index.localname) + def register(app): app.add_directive('dfhack-tool', DFHackToolDirective) app.add_directive('dfhack-command', DFHackCommandDirective) + update_index_titles(app) _KEYBINDS.update(scan_all_keybinds(os.path.join(dfhack.util.DFHACK_ROOT, 'data', 'init'))) - def setup(app): app.connect('builder-inited', register) From 4ae1b7fb747245df0358cc85a0778641ab536ea4 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 23 Sep 2022 12:51:50 -0700 Subject: [PATCH 708/854] fix anchor text transformation --- docs/sphinx_extensions/dfhack/tool_docs.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 581cee26e..62569a4d1 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -110,6 +110,14 @@ def check_missing_keybinds(): logger.warning('Undocumented keybindings for command: %s', missing_command) +_anchor_pattern = re.compile(r'^\d+') + +def to_anchor(name: str) -> str: + name = name.lower() + name = name.replace('/', '-') + name = re.sub(_anchor_pattern, '', name) + return name + class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): has_content = False required_arguments = 0 @@ -129,7 +137,7 @@ class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): def add_index_entries(self, name) -> None: docname = self.env.docname - anchor = self.get_tool_name_from_docname().replace('/', '-') + anchor = to_anchor(self.get_tool_name_from_docname()) tags = self.env.domaindata['tag-repo']['doctags'][docname] indexdata = (name, self.options.get('summary', ''), '', docname, anchor, 0) self.env.domaindata['all']['objects'].append(indexdata) @@ -293,7 +301,7 @@ def update_index_titles(app): if app.builder.format == 'html': index.localname = '"%s" tag index

%s

' % (index.shortname, index.localname) else: - index.localname = '%s tag index - %s' % (index.shortname, index.localname) + index.localname = '"%s" tag index - %s' % (index.shortname, index.localname) def register(app): app.add_directive('dfhack-tool', DFHackToolDirective) From 50f0d1137215d4505c837a732fe6548f8bf64c55 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 23 Sep 2022 13:19:13 -0700 Subject: [PATCH 709/854] include tool desc in the index for all formats --- docs/sphinx_extensions/dfhack/tool_docs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 62569a4d1..18532c9d5 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -260,8 +260,11 @@ def tag_index_generate(self, docnames: Optional[Iterable[str]] = None) -> Tuple[ content = defaultdict(list) for name, desc, _, docname, anchor, _ in self.domain.data['objects']: first_letter = name[0].lower() + extra, descr = desc, '' + if self.domain.env.app.builder.format == 'html': + extra, descr = '', desc content[first_letter].append( - IndexEntry(name, 0, docname, anchor, '', '', desc)) + IndexEntry(name, 0, docname, anchor, extra, '', descr)) return (sorted(content.items()), False) def register_index(app, tag, title): From 6b219f342dda546496a948625dd22bfeae0c876f Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 23 Sep 2022 14:09:15 -0700 Subject: [PATCH 710/854] add a TOC of all tools so the pdf picks them up --- docs/Tools.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/Tools.rst b/docs/Tools.rst index 20ceea769..401276d7f 100644 --- a/docs/Tools.rst +++ b/docs/Tools.rst @@ -50,3 +50,14 @@ DFHack tools by what they affect -------------------------------- .. include:: tags/bywhat.rst + +All DFHack tools alphabetically +------------------------------- + +.. toctree:: + :glob: + :maxdepth: 1 + :titlesonly: + + tools/* + tools/*/* From 8546b2963cd7970878ae8b5f1f38f19afd114fdc Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 23 Sep 2022 15:58:31 -0700 Subject: [PATCH 711/854] tool docs are no longer toc-orphaned --- conf.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/conf.py b/conf.py index 2742e18f0..661b0daea 100644 --- a/conf.py +++ b/conf.py @@ -73,13 +73,11 @@ def write_tool_docs(): the original documentation. """ for k in doc_all_dirs(): - header = ':orphan:\n' label = ('.. _{name}:\n\n').format(name=k[0]) include = ('.. include:: /{path}\n\n').format(path=k[1]) os.makedirs(os.path.join('docs/tools', os.path.dirname(k[0])), mode=0o755, exist_ok=True) with write_file_if_changed('docs/tools/{}.rst'.format(k[0])) as outfile: - outfile.write(header) if k[0] != 'search': outfile.write(label) outfile.write(include) From b1801d25ca4285646cb7c6e647ea88614241ebfb Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 23 Sep 2022 15:59:25 -0700 Subject: [PATCH 712/854] update scripts ref --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 8c03de45b..a1374b572 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 8c03de45ba8e772239224b49af2617fc714fbaa9 +Subproject commit a1374b5726b3dd682ac41d21701f186e748c0974 From d0f9b1d324bfeb52e605153ef462e76f04a56849 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 23 Sep 2022 16:01:33 -0700 Subject: [PATCH 713/854] update scripts ref --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index a1374b572..f318f56f7 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a1374b5726b3dd682ac41d21701f186e748c0974 +Subproject commit f318f56f75aa40f2bd91c1cd32c3b426f7821db4 From c4ebd195e8185b81aa6080b0d7e1fd939726ac0a Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 23 Sep 2022 16:02:26 -0700 Subject: [PATCH 714/854] update scripts ref --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index f318f56f7..00fb32809 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit f318f56f75aa40f2bd91c1cd32c3b426f7821db4 +Subproject commit 00fb328091d2950257aa576b223ca22aa621332b From 9eaf3acb256b97220d8b06fff0586e261917d6ff Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 23 Sep 2022 16:09:46 -0700 Subject: [PATCH 715/854] ensure readthedocs checks out submodules --- .readthedocs.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 1e570e571..e66d3da1b 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -9,13 +9,13 @@ build: tools: python: "3" +submodules: + include: all + sphinx: configuration: conf.py -formats: - - htmlzip - - pdf - - epub +formats: all python: install: From 65500e60690ce803366a6d820b87859dbb018bed Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 23 Sep 2022 16:21:47 -0700 Subject: [PATCH 716/854] update scripts ref --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 00fb32809..f02f2eb92 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 00fb328091d2950257aa576b223ca22aa621332b +Subproject commit f02f2eb9231c94385750fd318dfb85eed47be66f From e6336e769a2625ee55c9435f2be5c773f758dc37 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 4 Sep 2022 23:08:56 -0400 Subject: [PATCH 717/854] Units::teleport(): set idle_area --- docs/changelog.txt | 1 + library/modules/Units.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 895f7db72..813dfee77 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -80,6 +80,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Removed ``Windows`` module (C++-only) - unused. - ``Constructions`` module (C++-only): removed ``t_construction``, ``isValid()``, ``getCount()``, ``getConstruction()``, and ``copyConstruction()``. Access ``world.constructions`` directly instead. - ``Gui::getSelectedItem()``, ``Gui::getAnyItem()``: added support for the artifacts screen +- ``Units::teleport()``: now sets ``unit.idle_area`` ## Lua - History: added ``dfhack.getCommandHistory(history_id, history_filename)`` and ``dfhack.addCommandToHistory(history_id, history_filename, command)`` so gui scripts can access a commandline history without requiring a terminal. diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 8e9f624ea..32b9f28e5 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -174,6 +174,7 @@ bool Units::teleport(df::unit *unit, df::coord target_pos) // move unit to destination unit->pos = target_pos; + unit->idle_area = target_pos; // move unit's riders (including babies) to destination if (unit->flags1.bits.ridden) From c1b9ffc7ca7cef93ebaa0554f39a1fb40d10ef77 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 24 Sep 2022 13:31:18 -0400 Subject: [PATCH 718/854] Clarify changelog Based on clarification from Doublestrafe and Quietust --- docs/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 813dfee77..b7d822801 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -80,7 +80,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Removed ``Windows`` module (C++-only) - unused. - ``Constructions`` module (C++-only): removed ``t_construction``, ``isValid()``, ``getCount()``, ``getConstruction()``, and ``copyConstruction()``. Access ``world.constructions`` directly instead. - ``Gui::getSelectedItem()``, ``Gui::getAnyItem()``: added support for the artifacts screen -- ``Units::teleport()``: now sets ``unit.idle_area`` +- ``Units::teleport()``: now sets ``unit.idle_area`` to discourage units from walking back to their original location (or teleporting back, if using `fastdwarf`) ## Lua - History: added ``dfhack.getCommandHistory(history_id, history_filename)`` and ``dfhack.addCommandToHistory(history_id, history_filename, command)`` so gui scripts can access a commandline history without requiring a terminal. From e9eeb5fda03b24bd9455963a8d2c40b7ac201ccd Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 24 Sep 2022 13:59:04 -0400 Subject: [PATCH 719/854] tool_docs: don't assume cwd is dfhack root hopefully fixes the CI docs build in dfhack/scripts --- docs/sphinx_extensions/dfhack/tool_docs.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 18532c9d5..836bab217 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -225,7 +225,7 @@ def get_tags(): groups = {} group_re = re.compile(r'"([^"]+)"') tag_re = re.compile(r'- `([^ ]+) <[^>]+>`: (.*)') - with open('docs/Tags.rst') as f: + with open(os.path.join(dfhack.util.DOCS_ROOT, 'Tags.rst')) as f: lines = f.readlines() for line in lines: line = line.strip() @@ -285,10 +285,11 @@ def register_index(app, tag, title): app.add_index_to_domain(tag, index_class) def init_tag_indices(app): - os.makedirs('docs/tags', mode=0o755, exist_ok=True) + os.makedirs(os.path.join(dfhack.util.DOCS_ROOT, 'tags'), mode=0o755, exist_ok=True) tag_groups = get_tags() for tag_group in tag_groups: - with dfhack.util.write_file_if_changed(('docs/tags/by{group}.rst').format(group=tag_group)) as topidx: + group_file_path = os.path.join(dfhack.util.DOCS_ROOT, 'tags', 'by{group}.rst'.format(group=tag_group)) + with dfhack.util.write_file_if_changed(group_file_path) as topidx: for tag_tuple in tag_groups[tag_group]: tag, desc = tag_tuple[0], tag_tuple[1] topidx.write(('- `{name} <{name}-tag-index>`\n').format(name=tag)) From ea7326a1c852db60c575e504c45a2eee861243cd Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 24 Sep 2022 20:47:16 -0700 Subject: [PATCH 720/854] add more job types to the example prioritize list --- data/examples/init/onMapLoad_dreamfort.init | 26 +++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/data/examples/init/onMapLoad_dreamfort.init b/data/examples/init/onMapLoad_dreamfort.init index 05d32134f..619349986 100644 --- a/data/examples/init/onMapLoad_dreamfort.init +++ b/data/examples/init/onMapLoad_dreamfort.init @@ -1,6 +1,7 @@ # This dfhack config file automates common tasks for your forts. # It was written for the Dreamfort set of quickfort blueprints, but the -# configuration here is useful for any fort! Feed free to edit or override +# configuration here is useful for any fort! Copy this file to your +# dfhack-config/init directory to use. Feed free to edit or override # to your liking. # Uncomment this next line if you want buildingplan (and quickfort) to use only @@ -48,13 +49,24 @@ seedwatch all 30 # ensures important tasks get assigned to workers. # otherwise these job types can get ignored in busy forts. -prioritize -a StoreItemInVehicle StoreItemInBag StoreItemInBarrel PullLever -prioritize -a StoreItemInLocation StoreItemInHospital -prioritize -a DestroyBuilding RemoveConstruction RecoverWounded DumpItem -prioritize -a CleanSelf SlaughterAnimal PrepareRawFish ExtractFromRawFish -prioritize -a TradeAtDepot BringItemToDepot CleanTrap ManageWorkOrders +# +# take care of rottables before they rot prioritize -a --haul-labor=Food,Body StoreItemInStockpile prioritize -a --reaction-name=TAN_A_HIDE CustomReaction +prioritize -a PrepareRawFish +# +# organize items efficiently so new items can be brought to the stockpiles +prioritize -a StoreItemInVehicle StoreItemInBag StoreItemInBarrel +prioritize -a StoreItemInLocation StoreItemInHospital StoreItemInBin +# +# when these things come up, get them done ASAP +prioritize -a ManageWorkOrders TradeAtDepot BringItemToDepot CleanTrap +prioritize -a DestroyBuilding RemoveConstruction RecoverWounded DumpItem +prioritize -a PullLever CleanSelf SlaughterAnimal CollectSand +prioritize -a PenLargeAnimal PenSmallAnimal PitLargeAnimal PitSmallAnimal +prioritize -a TameAnimal TrainAnimal TrainHuntingAnimal TrainWarAnimal +prioritize -a FellTree FireBallista FireCatapult OperatePump +prioritize -a MakeArmor MakeWeapon # autobutcher settings are saved in the savegame, so we only need to set them once. # this way, any custom settings you set during gameplay are not overwritten @@ -78,7 +90,7 @@ on-new-fortress autobutcher target 50 50 14 2 BIRD_GOOSE on-new-fortress autobutcher target 2 2 4 2 ALPACA SHEEP LLAMA # pigs give milk and meat and are zero-maintenance. on-new-fortress autobutcher target 5 5 6 2 PIG -# butcher all unprofitable animals +# immediately butcher all unprofitable animals on-new-fortress autobutcher target 0 0 0 0 HORSE YAK DONKEY WATER_BUFFALO GOAT CAVY BIRD_DUCK BIRD_GUINEAFOWL # watch for new animals on-new-fortress autobutcher autowatch From 07d3e70b8a1a3b24799aa2e2f5942dba4341b0f4 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sun, 25 Sep 2022 07:19:50 +0000 Subject: [PATCH 721/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index f02f2eb92..a5b542eef 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit f02f2eb9231c94385750fd318dfb85eed47be66f +Subproject commit a5b542eef784fc86618c824052f60ecf2582833f From cb80f7dd756aa434bff836a37c4088edfdcfc2af Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 25 Sep 2022 16:04:36 -0700 Subject: [PATCH 722/854] don't cache dup civzones when scanning buildings --- docs/changelog.txt | 1 + library/modules/Buildings.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index b7d822801..52e906486 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -49,6 +49,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `dig-now`: Fix direction of smoothed walls when adjacent to a door or floodgate - ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled - `quickfort`: `Dreamfort ` blueprint set: declare the hospital zone before building the coffer; otherwise DF fails to stock the hospital with materials +- ``dfhack.buildings.findCivzonesAt``: no longer return duplicate civzones after loading a save with existing civzones ## Misc Improvements - Init scripts: ``dfhack.init`` and other init scripts have moved to ``dfhack-config/init/``. If you have customized your ``dfhack.init`` file and want to keep your changes, please move the part that you have customized to the new location at ``dfhack-config/init/dfhack.init``. If you do not have changes that you want to keep, do not copy anything, and the new defaults will be used automatically. diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index dd8d5f0e9..1918771ae 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -308,9 +308,8 @@ df::building *Buildings::findAtTile(df::coord pos) static unordered_map corner1; static unordered_map corner2; -static void cacheBuilding(df::building *building) { +static void cacheBuilding(df::building *building, bool is_civzone) { int32_t id = building->id; - bool is_civzone = !building->isSettingOccupancy(); df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z); df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z); @@ -344,7 +343,7 @@ static void cacheNewCivzones() { auto &vec = world->buildings.other[buildings_other_id::ANY_ZONE]; int32_t idx = df::building::binsearch_index(vec, id); if (idx > -1) - cacheBuilding(vec[idx]); + cacheBuilding(vec[idx], true); } nextCivzone = nextBuildingId; } @@ -1311,8 +1310,9 @@ void Buildings::updateBuildings(color_ostream&, void* ptr) if (building) { - if (!corner1.count(id)) - cacheBuilding(building); + bool is_civzone = !building->isSettingOccupancy(); + if (!corner1.count(id) && !is_civzone) + cacheBuilding(building, false); } else if (corner1.count(id)) { From fce4c9aa97b71ba1dd8ae32d8552e2313a6ac422 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 26 Sep 2022 07:36:25 +0000 Subject: [PATCH 723/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index a5b542eef..086409c42 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a5b542eef784fc86618c824052f60ecf2582833f +Subproject commit 086409c42cdf83e1be8631345b192bb60012939e From b744d1a8e5cbe4144a9331467d880fc20e007c10 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 26 Sep 2022 22:08:55 -0700 Subject: [PATCH 724/854] add medical jobs to the priority list as per discussion on reddit https://www.reddit.com/r/dwarffortress/comments/xj00e9/science_job_priority/ --- data/examples/init/onMapLoad_dreamfort.init | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/data/examples/init/onMapLoad_dreamfort.init b/data/examples/init/onMapLoad_dreamfort.init index 619349986..e23b28285 100644 --- a/data/examples/init/onMapLoad_dreamfort.init +++ b/data/examples/init/onMapLoad_dreamfort.init @@ -55,18 +55,25 @@ prioritize -a --haul-labor=Food,Body StoreItemInStockpile prioritize -a --reaction-name=TAN_A_HIDE CustomReaction prioritize -a PrepareRawFish # +# ensure medical, hygiene, and hospice tasks get done +prioritize -a CleanSelf RecoverWounded ApplyCast BringCrutch CleanPatient +prioritize -a DiagnosePatient DressWound GiveFood GiveWater ImmobilizeBreak +prioritize -a PlaceInTraction SetBone Surgery Suture +# # organize items efficiently so new items can be brought to the stockpiles prioritize -a StoreItemInVehicle StoreItemInBag StoreItemInBarrel prioritize -a StoreItemInLocation StoreItemInHospital StoreItemInBin # -# when these things come up, get them done ASAP -prioritize -a ManageWorkOrders TradeAtDepot BringItemToDepot CleanTrap -prioritize -a DestroyBuilding RemoveConstruction RecoverWounded DumpItem -prioritize -a PullLever CleanSelf SlaughterAnimal CollectSand -prioritize -a PenLargeAnimal PenSmallAnimal PitLargeAnimal PitSmallAnimal +# ensure prisoners and animals are tended to quickly +prioritize -a --haul-labor=Animals StoreItemInStockpile prioritize -a TameAnimal TrainAnimal TrainHuntingAnimal TrainWarAnimal +prioritize -a PenLargeAnimal PitLargeAnimal SlaughterAnimal +# +# when these things come up, get them done ASAP +prioritize -a ManageWorkOrders TradeAtDepot BringItemToDepot +prioritize -a DestroyBuilding RemoveConstruction DumpItem PullLever prioritize -a FellTree FireBallista FireCatapult OperatePump -prioritize -a MakeArmor MakeWeapon +prioritize -a CollectSand MakeArmor MakeWeapon # autobutcher settings are saved in the savegame, so we only need to set them once. # this way, any custom settings you set during gameplay are not overwritten From 2cf23e2b3489715e5b59f2fa019823040d73a822 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 27 Sep 2022 07:41:21 +0000 Subject: [PATCH 725/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 086409c42..4f568fc83 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 086409c42cdf83e1be8631345b192bb60012939e +Subproject commit 4f568fc8331d62bc6646055c1252bba19b3a526d From a0cc040d9c62539aa21787291c9585d07c97aede Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 28 Sep 2022 12:15:25 -0700 Subject: [PATCH 726/854] use new prioritize defaults --- data/examples/init/onMapLoad_dreamfort.init | 26 +-------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/data/examples/init/onMapLoad_dreamfort.init b/data/examples/init/onMapLoad_dreamfort.init index e23b28285..fe2b1d54e 100644 --- a/data/examples/init/onMapLoad_dreamfort.init +++ b/data/examples/init/onMapLoad_dreamfort.init @@ -49,31 +49,7 @@ seedwatch all 30 # ensures important tasks get assigned to workers. # otherwise these job types can get ignored in busy forts. -# -# take care of rottables before they rot -prioritize -a --haul-labor=Food,Body StoreItemInStockpile -prioritize -a --reaction-name=TAN_A_HIDE CustomReaction -prioritize -a PrepareRawFish -# -# ensure medical, hygiene, and hospice tasks get done -prioritize -a CleanSelf RecoverWounded ApplyCast BringCrutch CleanPatient -prioritize -a DiagnosePatient DressWound GiveFood GiveWater ImmobilizeBreak -prioritize -a PlaceInTraction SetBone Surgery Suture -# -# organize items efficiently so new items can be brought to the stockpiles -prioritize -a StoreItemInVehicle StoreItemInBag StoreItemInBarrel -prioritize -a StoreItemInLocation StoreItemInHospital StoreItemInBin -# -# ensure prisoners and animals are tended to quickly -prioritize -a --haul-labor=Animals StoreItemInStockpile -prioritize -a TameAnimal TrainAnimal TrainHuntingAnimal TrainWarAnimal -prioritize -a PenLargeAnimal PitLargeAnimal SlaughterAnimal -# -# when these things come up, get them done ASAP -prioritize -a ManageWorkOrders TradeAtDepot BringItemToDepot -prioritize -a DestroyBuilding RemoveConstruction DumpItem PullLever -prioritize -a FellTree FireBallista FireCatapult OperatePump -prioritize -a CollectSand MakeArmor MakeWeapon +prioritize -aq defaults # autobutcher settings are saved in the savegame, so we only need to set them once. # this way, any custom settings you set during gameplay are not overwritten From b6acf7a9286b3dd9809cbd72ea318f270ebe4cdf Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 28 Sep 2022 12:45:57 -0700 Subject: [PATCH 727/854] address feedback on the docs --- docs/plugins/3dveins.rst | 8 ++++---- docs/plugins/add-spatter.rst | 3 +++ docs/plugins/autohauler.rst | 4 ++-- docs/plugins/autolabor.rst | 9 +++++---- docs/plugins/autonestbox.rst | 14 +++++++------- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index f71a14343..96fbcf836 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -25,10 +25,10 @@ Example 3dveins -New veins are generated using 3D Perlin noise in order to produce a layout that -flows smoothly between z-levels. The vein distribution is based on the world -seed, so running the command for the second time should produce no change. It is -best to run it just once immediately after embark. +New veins are generated using natural-looking 3D Perlin noise in order to +produce a layout that flows smoothly between z-levels. The vein distribution is +based on the world seed, so running the command for the second time should +produce no change. It is best to run it just once immediately after embark. This command is intended as only a cosmetic change, so it takes care to exactly preserve the mineral counts reported by ``prospect all``. The amounts of layer diff --git a/docs/plugins/add-spatter.rst b/docs/plugins/add-spatter.rst index 873bd761f..1ce32fcbe 100644 --- a/docs/plugins/add-spatter.rst +++ b/docs/plugins/add-spatter.rst @@ -12,3 +12,6 @@ names starting with ``SPATTER_ADD_``, so there are no commands to run to use it. These reactions will then produce contaminants on items instead of improvements. The contaminants are immune to being washed away by water or destroyed by `clean`. + +You must have a mod installed that adds the appropriate tokens in order for this +plugin to do anything. diff --git a/docs/plugins/autohauler.rst b/docs/plugins/autohauler.rst index a71d3ed02..08c84327a 100644 --- a/docs/plugins/autohauler.rst +++ b/docs/plugins/autohauler.rst @@ -5,7 +5,7 @@ autohauler :summary: Automatically manage hauling labors. :tags: fort auto labors -Similar to `autolabor`, but instead of managing all labors, ``autohauler`` only +Similar to `autolabor`, but instead of managing all labors, autohauler only addresses hauling labors, leaving the assignment of skilled labors entirely up to you. You can use the in-game `manipulator` UI or an external tool like Dwarf Therapist to do so. @@ -19,7 +19,7 @@ assignment, with most skilled labors only being assigned to just a few dwarves and almost every non-military dwarf having at least one skilled labor assigned. Autohauler allows a skill to be used as a flag to exempt a dwarf from -``autohauler``'s effects. By default, this is the unused ALCHEMIST labor, but it +autohauler's effects. By default, this is the unused ALCHEMIST labor, but it can be changed by the user. Usage diff --git a/docs/plugins/autolabor.rst b/docs/plugins/autolabor.rst index ed39c2666..861655ab4 100644 --- a/docs/plugins/autolabor.rst +++ b/docs/plugins/autolabor.rst @@ -45,10 +45,11 @@ and manager. Hunting is never assigned without a butchery, and fishing is never assigned without a fishery. -For each labor, a preference order is calculated based on skill, biased against -masters of other trades and excluding those who can't do the job. The labor is -then added to the best dwarves for that labor, then to additional -dwarfs that meet any of these conditions: +For each labor, a preference order is calculated based on skill, excluding those +who can't do the job. Dwarves who are masters of particular skills are +deprioritized in the preference list for other skills. The labor is then added +to the best dwarves for that labor, then to additional dwarfs that +meet any of these conditions: * The dwarf is idle and there are no idle dwarves assigned to this labor * The dwarf has non-zero skill associated with the labor diff --git a/docs/plugins/autonestbox.rst b/docs/plugins/autonestbox.rst index 9d5683f2b..27074fe1c 100644 --- a/docs/plugins/autonestbox.rst +++ b/docs/plugins/autonestbox.rst @@ -5,13 +5,13 @@ autonestbox :summary: Auto-assign egg-laying female pets to nestbox zones. :tags: fort auto animals -To use this feature, you must create pen/pasture zones above nestboxes. If the -pen is bigger than 1x1, the nestbox must be in the top left corner. Only 1 unit -will be assigned per pen, regardless of the size. Egg layers who are also -grazers will be ignored, since confining them to a 1x1 pasture is not a good -idea. Only tame and domesticated own units are processed since pasturing -half-trained wild egg layers could destroy your neat nestbox zones when they -revert to wild. +To use this feature, you must create pen/pasture zones on the same tiles as +built nestboxes. If the pen is bigger than 1x1, the nestbox must be in the top +left corner. Only 1 unit will be assigned per pen, regardless of the size. Egg +layers who are also grazers will be ignored, since confining them to a 1x1 +pasture is not a good idea. Only tame and domesticated own units are processed +since pasturing half-trained wild egg layers could destroy your neat nestbox +zones when they revert to wild. Note that the age of the units is not checked, so you might get some egg-laying kids assigned to the nestbox zones. Most birds grow up quite fast, though, so From 9a2cb5ea4492783d4b8cb52c49fea92dad402838 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 28 Sep 2022 14:49:03 -0700 Subject: [PATCH 728/854] fix ALL the typos done by copying everything into Google docs and running spellcheck --- docs/Compile.rst | 8 ++-- docs/Documentation.rst | 2 +- docs/Introduction.rst | 4 +- docs/Lua API.rst | 58 ++++++++++++------------- docs/NEWS.rst | 2 +- docs/Structures-intro.rst | 2 +- docs/Tags.rst | 2 +- docs/guides/modding-guide.rst | 10 ++--- docs/guides/quickfort-alias-guide.rst | 6 +-- docs/guides/quickfort-library-guide.rst | 2 +- docs/guides/quickfort-user-guide.rst | 10 ++--- docs/plugins/autobutcher.rst | 4 +- docs/plugins/autolabor.rst | 6 +-- docs/plugins/automaterial.rst | 4 +- docs/plugins/burrows.rst | 2 +- docs/plugins/changelayer.rst | 2 +- docs/plugins/changevein.rst | 2 +- docs/plugins/command-prompt.rst | 2 +- docs/plugins/cursecheck.rst | 2 +- docs/plugins/debug.rst | 8 ++-- docs/plugins/dwarfmonitor.rst | 2 +- docs/plugins/dwarfvet.rst | 12 ++--- docs/plugins/embark-tools.rst | 2 +- docs/plugins/fix-unit-occupancy.rst | 4 +- docs/plugins/forceequip.rst | 2 +- docs/plugins/labormanager.rst | 4 +- docs/plugins/lair.rst | 2 +- docs/plugins/manipulator.rst | 2 +- docs/plugins/map-render.rst | 2 +- docs/plugins/power-meter.rst | 2 +- docs/plugins/regrass.rst | 2 +- docs/plugins/rendermax.rst | 2 +- docs/plugins/resume.rst | 2 +- docs/plugins/search.rst | 2 +- docs/plugins/seedwatch.rst | 2 +- docs/plugins/stocks.rst | 2 +- docs/plugins/tiletypes.rst | 2 +- docs/plugins/tubefill.rst | 2 +- docs/plugins/tweak.rst | 2 +- docs/plugins/workflow.rst | 2 +- docs/plugins/zone.rst | 4 +- 41 files changed, 98 insertions(+), 98 deletions(-) diff --git a/docs/Compile.rst b/docs/Compile.rst index 0f3880816..ae23a1b73 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -289,7 +289,7 @@ DF, which causes DF to use your system libstdc++ instead:: rm libs/libstdc++.so.6 Note that distributing binaries compiled with newer GCC versions may result in -the opposite compatibily issue: users with *older* GCC versions may encounter +the opposite compatibility issue: users with *older* GCC versions may encounter similar errors. This is why DFHack distributes both GCC 4.8 and GCC 7 builds. If you are planning on distributing binaries to other users, we recommend using an older GCC (but still at least 4.8) version if possible. @@ -314,7 +314,7 @@ Notes for GCC 8+ or OS X 10.10+ users If none of these situations apply to you, skip to `osx-setup`. -If you have issues building on OS X 10.10 (Yosemite) or above, try definining +If you have issues building on OS X 10.10 (Yosemite) or above, try defining the following environment variable:: export MACOSX_DEPLOYMENT_TARGET=10.9 @@ -503,7 +503,7 @@ in their name. If this redirect doesn't occur, just copy, paste, and enter the download link again and you should see the options. You need to get: Visual C++ Build Tools for Visual Studio 2015 with Update 3. Click the download button next to it and a dropdown of download formats will appear. -Select the DVD format to download an ISO file. When the donwload is complete, +Select the DVD format to download an ISO file. When the download is complete, click on the ISO file and a folder will popup with the following contents: * packages (folder) @@ -561,7 +561,7 @@ Additional dependencies: installing with the Chocolatey Package Manager ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The remainder of dependencies - Git, CMake, StrawberryPerl, and Python - can be -most easily installed using the Chocolatey Package Manger. Chocolatey is a +most easily installed using the Chocolatey Package Manager. Chocolatey is a \*nix-style package manager for Windows. It's fast, small (8-20MB on disk) and very capable. Think "``apt-get`` for Windows." diff --git a/docs/Documentation.rst b/docs/Documentation.rst index 13ae58dca..6ac5ef382 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -76,7 +76,7 @@ replaced with the corresponding title and linked: e.g. ```autolabor``` => `autolabor`. Scripts and plugins have link targets that match their names created for you automatically. -If you want to link to a heading in your own page, you can specifiy it like this:: +If you want to link to a heading in your own page, you can specify it like this:: `Heading text exactly as written`_ diff --git a/docs/Introduction.rst b/docs/Introduction.rst index d90983d8c..798a9c3c8 100644 --- a/docs/Introduction.rst +++ b/docs/Introduction.rst @@ -28,7 +28,7 @@ or for coexistence in a single DF install, even with incompatible components. For developers, DFHack unites the various ways tools access DF memory and allows easier development of new tools. As an open-source project under -`various open-source licences `, contributions are welcome. +`various open-source licenses `, contributions are welcome. .. contents:: Contents @@ -54,7 +54,7 @@ used by the DFHack console. to be used this way. * Commands can also run at startup via `init files `, - on in batches at other times with the `script` command. + or in batches at other times with the `script` command. * Finally, some commands are persistent once enabled, and will sit in the background managing or changing some aspect of the game if you `enable` them. diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 64ccdd595..4aa8d78cb 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -740,7 +740,7 @@ Functions: * ``dfhack.matinfo.decode(type,index)`` - Looks up material info for the given number pair; if not found, returs *nil*. + Looks up material info for the given number pair; if not found, returns *nil*. * ``....decode(matinfo)``, ``....decode(item)``, ``....decode(obj)`` @@ -1099,7 +1099,7 @@ Other * ``dfhack.gui.getDepthAt(x, y)`` Returns the distance from the z-level of the tile at map coordinates (x, y) to - the closest ground z-level below. Defaults to 0, unless overriden by plugins. + the closest ground z-level below. Defaults to 0, unless overridden by plugins. Job module ---------- @@ -1869,7 +1869,7 @@ Among them are: - ``full_rectangle = true`` For buildings like stockpiles or farm plots that can normally - accomodate individual tile exclusion, forces an error if any + accommodate individual tile exclusion, forces an error if any tiles within the specified width*height are obstructed. - ``items = { item, item ... }``, or ``filters = { {...}, {...}... }`` @@ -2169,7 +2169,7 @@ Supported callbacks and fields are: Called when keyboard or mouse events are available. If any keys are pressed, the keys argument is a table mapping them to *true*. - Note that this refers to logical keybingings computed from real keys via + Note that this refers to logical keybindings computed from real keys via options; if multiple interpretations exist, the table will contain multiple keys. The table also may contain special keys: @@ -2494,7 +2494,7 @@ Core context specific functions: unit of time used, and may be one of ``'frames'`` (raw FPS), ``'ticks'`` (unpaused FPS), ``'days'``, ``'months'``, ``'years'`` (in-game time). All timers other than - ``'frames'`` are cancelled when the world is unloaded, + ``'frames'`` are canceled when the world is unloaded, and cannot be queued until it is loaded again. Returns the timer id, or *nil* if unsuccessful due to world being unloaded. @@ -2677,7 +2677,7 @@ environment by the mandatory init file dfhack.lua: .. _lua-string: -String class extentions +String class extensions ----------------------- DFHack extends Lua's basic string class to include a number of convenience @@ -2698,7 +2698,7 @@ functions. These are invoked just like standard string functions, e.g.:: * ``string:split([delimiter[, plain]])`` Split a string by the given delimiter. If no delimiter is specified, space - (``' '``) is used. The delimter is treated as a pattern unless a ``plain`` is + (``' '``) is used. The delimiter is treated as a pattern unless a ``plain`` is specified and set to ``true``. To treat multiple successive delimiter characters as a single delimiter, e.g. to avoid getting empty string elements, pass a pattern like ``' +'``. Be aware that passing patterns that match empty @@ -2975,10 +2975,10 @@ parameters. (e.g. combining the previous two examples into ``-abcdparam``) Long options focus on clarity. They are usually entire words, or several words - combined with hypens (``-``) or underscores (``_``). If they take an argument, - the argument can be separated from the option name by a space or an equals - sign (``=``). For example, the following two commandlines are equivalent: - ``yourscript --style pretty`` and ``yourscript --style=pretty``. + combined with hyphens (``-``) or underscores (``_``). If they take an + argument, the argument can be separated from the option name by a space or an + equals sign (``=``). For example, the following two commandlines are + equivalent: ``yourscript --style pretty`` and ``yourscript --style=pretty``. Another reason to use long options is if they represent an esoteric parameter that you don't expect to be commonly used and that you don't want to "waste" a @@ -3172,7 +3172,7 @@ create profiler objects which can be used to profile and generate report. * ``profiler.newProfiler([variant[, sampling_frequency]])`` - Returns an profile object with ``variant`` either ``'time'`` or ``'call'``. + Returns a profile object with ``variant`` either ``'time'`` or ``'call'``. ``'time'`` variant takes optional ``sampling_frequency`` parameter to select lua instruction counts between samples. Default is ``'time'`` variant with ``10*1000`` frequency. @@ -3257,7 +3257,7 @@ Implements a trivial single-inheritance class system. The main difference is that attributes are processed as a separate initialization step, before any ``init`` methods are called. They - also make the directy relation between instance fields and constructor + also make the direct relation between instance fields and constructor arguments more explicit. * ``new_obj = Class{ foo = arg, bar = arg, ... }`` @@ -3267,8 +3267,8 @@ Implements a trivial single-inheritance class system. 1. An empty instance table is created, and its metatable set. 2. The ``preinit`` methods are called via ``invoke_before`` (see below) - with the table used as argument to the class. These methods are intended - for validating and tweaking that argument table. + with the table used as the argument to the class. These methods are + intended for validating and tweaking that argument table. 3. Declared ATTRS are initialized from the argument table or their default values. 4. The ``init`` methods are called via ``invoke_after`` with the argument table. This is the main constructor method. @@ -3339,7 +3339,7 @@ A module for reading custom tokens added to the raws by mods. Where ``typeInstance`` is a unit, entity, item, job, projectile, building, plant, or interaction instance. Gets ``typeDefinition`` and then returns the same as ``getToken(typeDefinition, token)``. - For units, it gets the token from the race or caste instead if appplicable. For plants growth items, + For units, it gets the token from the race or caste instead if applicable. For plants growth items, it gets the token from the plant or plant growth instead if applicable. For plants it does the same but with growth number -1. @@ -3661,7 +3661,7 @@ It also always has the following fields: These fields are computed by the layout process: -:frame_parent_rect: The ViewRect represeting the client area of the parent view. +:frame_parent_rect: The ViewRect representing the client area of the parent view. :frame_rect: The ``mkdims`` rect of the outer frame in parent-local coordinates. :frame_body: The ViewRect representing the body part of the View's own frame. @@ -3897,13 +3897,13 @@ Base of all the widgets. Inherits from View and has the following attributes: :r: gap between the right edges of the frame and the parent. :b: gap between the bottom edges of the frame and the parent. :w: maximum width of the frame. - :h: maximum heigth of the frame. + :h: maximum height of the frame. :xalign: X alignment of the frame. :yalign: Y alignment of the frame. First the ``l,t,r,b`` fields restrict the available area for placing the frame. If ``w`` and ``h`` are not specified or - larger then the computed area, it becomes the frame. Otherwise + larger than the computed area, it becomes the frame. Otherwise the smaller frame is placed within the are based on the ``xalign/yalign`` fields. If the align hints are omitted, they are assumed to be 0, 1, or 0.5 based on which of the ``l/r/t/b`` @@ -4437,7 +4437,7 @@ Functions .. note:: this is the only mandatory field. :fix_impassible: - if true make impassible tiles impassible to liquids too + if true make impassable tiles impassable to liquids too :consume: how much machine power is needed to work. Disables reactions if not supplied enough and ``needs_power==1`` @@ -4461,7 +4461,7 @@ Functions :canBeRoomSubset: a flag if this building can be counted in room. 1 means it can, 0 means it can't and -1 default building behaviour :auto_gears: - a flag that automatically fills up gears and animate. It looks over building definition for gear icons and maps them. + a flag that automatically fills up gears and animations. It looks over the building definition for gear icons and maps them. Animate table also might contain: @@ -4472,7 +4472,7 @@ Functions ``getPower(building)`` returns two number - produced and consumed power if building can be modified and returns nothing otherwise -``setPower(building,produced,consumed)`` sets current productiona and consumption for a building. +``setPower(building,produced,consumed)`` sets current power production and consumption for a building. Examples -------- @@ -4506,7 +4506,7 @@ Native functions provided by the `buildingplan` plugin: * ``bool isPlanModeEnabled(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the buildingplan UI is enabled for the specified building type. * ``bool isPlannedBuilding(df::building *bld)`` returns whether the given building is managed by buildingplan. * ``void addPlannedBuilding(df::building *bld)`` suspends the building jobs and adds the building to the monitor list. -* ``void doCycle()`` runs a check for whether buildlings in the monitor list can be assigned items and unsuspended. This method runs automatically twice a game day, so you only need to call it directly if you want buildingplan to do a check right now. +* ``void doCycle()`` runs a check for whether buildings in the monitor list can be assigned items and unsuspended. This method runs automatically twice a game day, so you only need to call it directly if you want buildingplan to do a check right now. * ``void scheduleCycle()`` schedules a cycle to be run during the next non-paused game frame. Can be called multiple times while the game is paused and only one cycle will be scheduled. burrows @@ -4740,7 +4740,7 @@ List of events 1. ``onReactionCompleting(reaction,reaction_product,unit,input_items,input_reagents,output_items,call_native)`` - Is called once per reaction product, before reaction had a chance to call native code for item creation. + Is called once per reaction product, before the reaction has a chance to call native code for item creation. Setting ``call_native.value=false`` cancels further processing: no items are created and ``onReactionComplete`` is not called. 2. ``onReactionComplete(reaction,reaction_product,unit,input_items,input_reagents,output_items)`` @@ -4796,7 +4796,7 @@ These events are straight from EventManager module. Each of them first needs to 4. ``onJobCompleted(job)`` - Gets called when job is finished. The job that is passed to this function is a copy. Requires a frequency of 0 in order to distinguish between workshop jobs that were cancelled by the user and workshop jobs that completed successfully. + Gets called when job is finished. The job that is passed to this function is a copy. Requires a frequency of 0 in order to distinguish between workshop jobs that were canceled by the user and workshop jobs that completed successfully. 5. ``onUnitDeath(unit_id)`` @@ -4855,7 +4855,7 @@ Functions 5. ``registerSidebar(shop_name,callback)`` - Enable callback when sidebar for ``shop_name`` is drawn. Usefull for custom workshop views e.g. using gui.dwarfmode lib. Also accepts a ``class`` instead of function + Enable callback when sidebar for ``shop_name`` is drawn. Useful for custom workshop views e.g. using gui.dwarfmode lib. Also accepts a ``class`` instead of function as callback. Best used with ``gui.dwarfmode`` class ``WorkshopOverlay``. Examples @@ -4898,7 +4898,7 @@ luasocket ========= A way to access csocket from lua. The usage is made similar to luasocket in vanilla lua distributions. Currently -only subset of functions exist and only tcp mode is implemented. +only a subset of the functions exist and only tcp mode is implemented. .. contents:: :local: @@ -4977,7 +4977,7 @@ Functions - ``render_map_rect(x,y,z,w,h)`` - returns a table with w*h*4 entries of rendered tiles. The format is same as ``df.global.gps.screen`` (tile,foreground,bright,background). + returns a table with w*h*4 entries of rendered tiles. The format is the same as ``df.global.gps.screen`` (tile,foreground,bright,background). .. _pathable-api: @@ -5094,7 +5094,7 @@ Scripts :local: Any files with the ``.lua`` extension placed into the :file:`hack/scripts` folder -are automatically made avaiable as DFHack commands. The command corresponding to +are automatically made available as DFHack commands. The command corresponding to a script is simply the script's filename, relative to the scripts folder, with the extension omitted. For example: diff --git a/docs/NEWS.rst b/docs/NEWS.rst index fb33aebf9..13d5e38de 100644 --- a/docs/NEWS.rst +++ b/docs/NEWS.rst @@ -22,6 +22,6 @@ See `dev-changelog` for a list of changes grouped by development releases. Older Changelogs ================ -Are kept in a seperate file: `History` +Are kept in a separate file: `History` .. that's ``docs/History.rst``, if you're reading the raw text. diff --git a/docs/Structures-intro.rst b/docs/Structures-intro.rst index 48aa571c8..9d304949f 100644 --- a/docs/Structures-intro.rst +++ b/docs/Structures-intro.rst @@ -18,7 +18,7 @@ layout changes, and will need to be recompiled for every new DF version. Addresses of DF global objects and vtables are stored in a separate file, :file:`symbols.xml`. Since these are only absolute addresses, they do not need -to be compiled in to DFHack code, and are instead loaded at runtime. This makes +to be compiled into DFHack code, and are instead loaded at runtime. This makes fixes and additions to global addresses possible without recompiling DFHack. In an installed copy of DFHack, this file can be found at the root of the ``hack`` folder. diff --git a/docs/Tags.rst b/docs/Tags.rst index 45bfeb9b3..2ee18de48 100644 --- a/docs/Tags.rst +++ b/docs/Tags.rst @@ -43,6 +43,6 @@ for the tag assignment spreadsheet. - `map `: Tools that interact with the game map. - `military `: Tools that interact with the military. - `plants `: Tools that interact with trees, shrubs, and crops. -- `stockpiles `: Tools that interact wtih stockpiles. +- `stockpiles `: Tools that interact with stockpiles. - `units `: Tools that interact with units. - `workorders `: Tools that interact with workorders. diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 4b768be58..da33a5a9d 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -129,7 +129,7 @@ provides two libraries for this, ``repeat-util`` and `eventful `. ``repeat-util`` is used to run a function once per a configurable number of frames (paused or unpaused), ticks (unpaused), in-game days, months, or years. If you need to be aware the instant something happens, you'll need to run a -check once a tick. Be careful not to do this gratuitiously, though, since +check once a tick. Be careful not to do this gratuitously, though, since running that often can slow down the game! ``eventful``, on the other hand, is much more performance-friendly since it will @@ -176,10 +176,10 @@ you can react to with ``eventful``. Now, you may have noticed that you won't be able to register multiple callbacks with a single key named after your mod. You can, of course, call all the -functions you want from a single registed callback. Alternately, you can create -multiple callbacks using different keys, using your mod ID as a key name prefix. -If you do register multiple callbacks, though, there are no guarantees about the -call order. +functions you want from a single registered callback. Alternately, you can +create multiple callbacks using different keys, using your mod ID as a key name +prefix. If you do register multiple callbacks, though, there are no guarantees +about the call order. Custom raw tokens ----------------- diff --git a/docs/guides/quickfort-alias-guide.rst b/docs/guides/quickfort-alias-guide.rst index 50e5d7650..a89e6f338 100644 --- a/docs/guides/quickfort-alias-guide.rst +++ b/docs/guides/quickfort-alias-guide.rst @@ -80,7 +80,7 @@ sequence, potentially with other aliases. If the alias is the only text in the cell, the alias name is matched and its expansion is used. If the alias has other keys before or after it, the alias name must be surrounded in curly brackets (:kbd:`{` and :kbd:`}`). An alias can be surrounded in curly brackets -even if it is the only text in the cell, it just isn't necesary. For example, +even if it is the only text in the cell, it just isn't necessary. For example, the following blueprint uses the ``aliasname`` alias by itself in the first two rows and uses it as part of a longer sequence in the third row:: @@ -454,7 +454,7 @@ be used for either the ``quantum_enable`` or ``route_enable`` sub-aliases. Experienced Dwarf Fortress players may be wondering how the same aliases can work in both contexts since the keys for entering the configuration screen differ. Fear not! There is some sub-alias magic at work here. If you define -your own stockpile configuraiton aliases, you can use the magic yourself by +your own stockpile configuration aliases, you can use the magic yourself by building your aliases on the ``*prefix`` aliases described later in this guide. @@ -652,7 +652,7 @@ sheetprefix enablesheet disablesheet Then, for each item category, there are aliases that manipulate interesting subsets of that category: -* Exclusive aliases forbid everthing within a category and then enable only +* Exclusive aliases forbid everything within a category and then enable only the named item type (or named class of items) * ``forbid*`` aliases forbid the named type and leave the rest of the stockpile untouched. diff --git a/docs/guides/quickfort-library-guide.rst b/docs/guides/quickfort-library-guide.rst index 86c82ac60..157bb10a1 100644 --- a/docs/guides/quickfort-library-guide.rst +++ b/docs/guides/quickfort-library-guide.rst @@ -198,7 +198,7 @@ Light aquifer tap ~~~~~~~~~~~~~~~~~ The aquifer tap helps you create a safe, everlasting source of fresh water from -a light aquifer. See the step-by-step guide, including informaton on how to +a light aquifer. See the step-by-step guide, including information on how to create a drainage system so your dwarves don't drown when digging the tap, by running ``quickfort run library/aquifer_tap.csv -n /help``. diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 693ab033c..71a80818c 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -245,7 +245,7 @@ If there weren't an alias named ``booze`` then the literal characters spell those aliases correctly! You can save a lot of time and effort by using aliases instead of adding all -key seqences directly to your blueprints. For more details, check out the +key sequences directly to your blueprints. For more details, check out the `quickfort-alias-guide`. You can also see examples of aliases being used in the query blueprints in the :source:`DFHack blueprint library `. You can create @@ -683,7 +683,7 @@ three vertical tiles like this:: ` end here ` # # # # # -Then to carve the cross, you'd do a horizonal segment:: +Then to carve the cross, you'd do a horizontal segment:: ` ` ` # start here ` end here # @@ -740,7 +740,7 @@ Or you could use the aliases to specify tile by tile:: # # # # The aliases can also be used to designate a solid block of track. This is -epecially useful for obliterating low-quality engravings so you can re-smooth +especially useful for obliterating low-quality engravings so you can re-smooth and re-engrave with higher quality. For example, you could use the following sequence of blueprints to ensure a 10x10 floor area contains only masterwork engravings:: @@ -1157,7 +1157,7 @@ blueprint:: "#meta label(help) message(This is the help text for the blueprint set contained in this file. - First, make sure that you embark in...) blueprint set walkthough" + First, make sure that you embark in...) blueprint set walkthrough" could more naturally be written as a ``#notes`` blueprint:: @@ -1739,7 +1739,7 @@ priorities `. Use dig priorities to control ramp creation. We can `ensure `__ -the bottom level is carved out before the layer above is channelled by assigning +the bottom level is carved out before the layer above is channeled by assigning the channel designations lower priorities (the ``h5``\s in the third layer -- scroll down). diff --git a/docs/plugins/autobutcher.rst b/docs/plugins/autobutcher.rst index 83c99271a..f288f9073 100644 --- a/docs/plugins/autobutcher.rst +++ b/docs/plugins/autobutcher.rst @@ -6,7 +6,7 @@ autobutcher :tags: fort auto fps animals This plugin monitors how many pets you have of each gender and age and assigns -excess lifestock for slaughter. It requires that you add the target race(s) to a +excess livestock for slaughter. It requires that you add the target race(s) to a watch list. Units will be ignored if they are: * Untamed @@ -44,7 +44,7 @@ Usage - fa = number of female adults - ma = number of female adults If you specify ``all``, then this command will set the counts for all races - on your current watchlist (including the races which are currenly set to + on your current watchlist (including the races which are currently set to 'unwatched') and sets the new default for future watch commands. If you specify ``new``, then this command just sets the new default counts for future watch commands without changing your current watchlist. Otherwise, diff --git a/docs/plugins/autolabor.rst b/docs/plugins/autolabor.rst index 861655ab4..b39884957 100644 --- a/docs/plugins/autolabor.rst +++ b/docs/plugins/autolabor.rst @@ -11,7 +11,7 @@ dwarves to specialize in specific skills. Autolabor frequently checks how many jobs of each type are available and sets labors proportionally in order to get them all done quickly. Labors with equipment -- mining, hunting, and woodcutting -- which are abandoned if labors -change mid-job, are handled slightly differently to minimise churn. +change mid-job, are handled slightly differently to minimize churn. Dwarves on active military duty or dwarves assigned to burrows are left untouched by autolabor. @@ -39,7 +39,7 @@ and filling ponds. Other jobs are automatically assigned as described above. Each of these settings can be adjusted. Jobs are rarely assigned to nobles with responsibilities for meeting diplomats -or merchants, never to the chief medical dwarf, and less often to the bookeeper +or merchants, never to the chief medical dwarf, and less often to the bookkeeper and manager. Hunting is never assigned without a butchery, and fishing is never assigned @@ -63,7 +63,7 @@ Examples ``autolabor MINE 5`` Keep at least 5 dwarves with mining enabled. ``autolabor CUT_GEM 1 1`` - Keep exactly 1 dwarf with gemcutting enabled. + Keep exactly 1 dwarf with gem cutting enabled. ``autolabor COOK 1 1 3`` Keep 1 dwarf with cooking enabled, selected only from the top 3. ``autolabor FEED_WATER_CIVILIANS haulers`` diff --git a/docs/plugins/automaterial.rst b/docs/plugins/automaterial.rst index 068664638..ac1b3a1da 100644 --- a/docs/plugins/automaterial.rst +++ b/docs/plugins/automaterial.rst @@ -48,6 +48,6 @@ construction menu after selecting materials, it returns you back to this screen. If you use this along with several autoselect enabled materials, you should be able to place complex constructions more conveniently. -The ``automaterial`` plugin also enables extra contruction placement modes, such -as designating areas larger than 10x10 and allowing you to designate hollow +The ``automaterial`` plugin also enables extra construction placement modes, +such as designating areas larger than 10x10 and allowing you to designate hollow rectangles instead of the default filled ones. diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst index 50d736b95..fb3876e97 100644 --- a/docs/plugins/burrows.rst +++ b/docs/plugins/burrows.rst @@ -34,7 +34,7 @@ Usage ``burrow remove-units target-burrow [ ...]`` Remove units in source burrows from the target. ``burrow set-tiles target-burrow [ ...]`` - Clear target burrow tiles and adds tiles from the names source burrows. + Clear target burrow tiles and add tiles from the names source burrows. ``burrow add-tiles target-burrow [ ...]`` Add tiles from the source burrows to the target. ``burrow remove-tiles target-burrow [ ...]`` diff --git a/docs/plugins/changelayer.rst b/docs/plugins/changelayer.rst index f9467315f..f986a1e3d 100644 --- a/docs/plugins/changelayer.rst +++ b/docs/plugins/changelayer.rst @@ -35,7 +35,7 @@ Examples ``changelayer GRANITE`` Convert the layer at the cursor position into granite. ``changelayer SILTY_CLAY force`` - Convert teh layer at the cursor position into clay, even if it's stone. + Convert the layer at the cursor position into clay, even if it's stone. ``changelayer MARBLE all_biomes all_layers`` Convert all layers of all biomes which are not soil into marble. diff --git a/docs/plugins/changevein.rst b/docs/plugins/changevein.rst index f033a4a6a..4e4512bb1 100644 --- a/docs/plugins/changevein.rst +++ b/docs/plugins/changevein.rst @@ -5,7 +5,7 @@ changevein :summary: Change the material of a mineral inclusion. :tags: fort armok map -You can change a vein to any incorganic material RAW id. Note that this command +You can change a vein to any inorganic material RAW id. Note that this command only affects tiles within the current 16x16 block - for large veins and clusters, you will need to use this command multiple times. diff --git a/docs/plugins/command-prompt.rst b/docs/plugins/command-prompt.rst index 7ccad6c71..c4b464a80 100644 --- a/docs/plugins/command-prompt.rst +++ b/docs/plugins/command-prompt.rst @@ -13,7 +13,7 @@ Usage command-prompt [entry] If called with parameters, it starts with that text in the command edit area. -This is most useful for developers, who can set a keybinding to open a laungage +This is most useful for developers, who can set a keybinding to open a language interpreter for lua or Ruby by starting with the `:lua ` or `:rb ` portions of the command already filled in. diff --git a/docs/plugins/cursecheck.rst b/docs/plugins/cursecheck.rst index 2d623241c..94012919b 100644 --- a/docs/plugins/cursecheck.rst +++ b/docs/plugins/cursecheck.rst @@ -33,7 +33,7 @@ Examples - ``cursecheck detail all`` Give detailed info about all cursed creatures including deceased ones. - ``cursecheck nick`` - Give a nickname all living/active cursed creatures. + Give a nickname to all living/active cursed creatures. .. note:: diff --git a/docs/plugins/debug.rst b/docs/plugins/debug.rst index 0e184595f..0c5a587fb 100644 --- a/docs/plugins/debug.rst +++ b/docs/plugins/debug.rst @@ -42,13 +42,13 @@ Usage ----- ``debugfilter category [] []`` - List available debug plugin and category names. If filters aren't givenm + List available debug plugin and category names. If filters aren't given then all plugins/categories are matched. This command is a good way to test regex parameters before you pass them to ``set``. ``debugfilter filter []`` List active and passive debug print level changes. The optional ``id`` - parameter is the id listed as first column in the filter list. If ``id`` is - given, then the command shows extended information for the given filter + parameter is the id listed as the first column in the filter list. If ``id`` + is given, then the command shows extended information for the given filter only. ``debugfilter set [] [] []`` Create a new debug filter to set category verbosity levels. This filter @@ -61,7 +61,7 @@ Usage ``debugfilter disable [ ...]`` Disable a space separated list of filters but keep it in the filter list. ``debugfilter enable [ ...]`` - Enable a space sperate list of filters. + Enable a space separated list of filters. ``debugfilter header [enable] | [disable] [ ...]`` Control which header metadata is shown along with each log message. Run it without parameters to see the list of configurable elements. Include an diff --git a/docs/plugins/dwarfmonitor.rst b/docs/plugins/dwarfmonitor.rst index bfdbd13b5..79472ae58 100644 --- a/docs/plugins/dwarfmonitor.rst +++ b/docs/plugins/dwarfmonitor.rst @@ -87,7 +87,7 @@ Some widgets support additional options: * ``cursor`` widget: * ``format``: Specifies the format. ``X``, ``x``, ``Y``, and ``y`` are - replaced with the corresponding cursor cordinates, while all other + replaced with the corresponding cursor coordinates, while all other characters are unmodified. * ``show_invalid``: If set to ``true``, the mouse coordinates will both be displayed as ``-1`` when the cursor is outside of the DF window; otherwise, diff --git a/docs/plugins/dwarfvet.rst b/docs/plugins/dwarfvet.rst index e7ddf73d6..fa165250d 100644 --- a/docs/plugins/dwarfvet.rst +++ b/docs/plugins/dwarfvet.rst @@ -5,12 +5,12 @@ dwarfvet :summary: Allows animals to be treated at animal hospitals. :tags: fort gameplay animals -Annoyed your dragons become useless after a minor injury? Well, with dwarfvet, -injured animals will be treated at an animal hospital, which is simply a hospital -that is also an animal training zone. Dwarfs with the Animal Caretaker labor -enabled will come to the hospital to treat animals. Normal medical skills are -used (and trained), but no experience is given to the Animal Caretaker skill -itself. +Annoyed that your dragons become useless after a minor injury? Well, with +dwarfvet, injured animals will be treated at an animal hospital, which is simply +a hospital that is also an animal training zone. Dwarfs with the Animal +Caretaker labor enabled will come to the hospital to treat animals. Normal +medical skills are used (and trained), but no experience is given to the Animal +Caretaker skill itself. Usage ----- diff --git a/docs/plugins/embark-tools.rst b/docs/plugins/embark-tools.rst index 64db27953..781d66e90 100644 --- a/docs/plugins/embark-tools.rst +++ b/docs/plugins/embark-tools.rst @@ -14,7 +14,7 @@ Usage embark-tools enable|disable all embark-tools enable|disable [ ...] -Avaliable tools are: +Available tools are: ``anywhere`` Allows embarking anywhere (including sites, mountain-only biomes, and diff --git a/docs/plugins/fix-unit-occupancy.rst b/docs/plugins/fix-unit-occupancy.rst index 991c77314..0559d4a2f 100644 --- a/docs/plugins/fix-unit-occupancy.rst +++ b/docs/plugins/fix-unit-occupancy.rst @@ -18,7 +18,7 @@ Usage fix-unit-occupancy interval When run without arguments (or with just the ``here`` or ``-n`` parameters), -the fix just runs once. You can also have it run periodically by enbling the +the fix just runs once. You can also have it run periodically by enabling the plugin. Examples @@ -35,7 +35,7 @@ Options ``here`` Only operate on the tile at the cursor. ``-n`` - Report issues, but do not any write changes to the map. + Report issues, but do not write any changes to the map. ``interval `` Set how often the plugin will check for and fix issues when it is enabled. The default is 1200 ticks, or 1 game day. diff --git a/docs/plugins/forceequip.rst b/docs/plugins/forceequip.rst index a32af524b..26ab378ee 100644 --- a/docs/plugins/forceequip.rst +++ b/docs/plugins/forceequip.rst @@ -64,7 +64,7 @@ Examples ``forceequip v bp QQQ`` List the bodyparts of the selected unit. ``forceequip bp LH`` - Equips an appopriate item onto the unit's left hand. + Equips an appropriate item onto the unit's left hand. ``forceequip m bp LH`` Equips ALL appropriate items onto the unit's left hand. The unit may end up wearing a dozen left-handed mittens. Use with caution, and remember that diff --git a/docs/plugins/labormanager.rst b/docs/plugins/labormanager.rst index 5c3632b6a..e3443280d 100644 --- a/docs/plugins/labormanager.rst +++ b/docs/plugins/labormanager.rst @@ -37,7 +37,7 @@ explicitly disable it, even if you save and reload your game. The default priorities for each labor vary (some labors are higher priority by default than others). The way the plugin works is that, once it determines how -many jobs of each labor is needed, it then sorts them by adjusted priority. +many jobs of each labor are needed, it then sorts them by adjusted priority. (Labors other than hauling have a bias added to them based on how long it's been since they were last used to prevent job starvation.) The labor with the highest priority is selected, the "best fit" dwarf for that labor is assigned to that @@ -112,7 +112,7 @@ Advanced usage ``labormanager priority `` Set the priority value for labor to . ``labormanager max `` - Set maximum number of dwarves that can be assigned to a labor. + Set the maximum number of dwarves that can be assigned to a labor. ``labormanager max none`` Unrestrict the number of dwarves that can be assigned to a labor. ``labormanager max disable`` diff --git a/docs/plugins/lair.rst b/docs/plugins/lair.rst index ed2816818..9bded57ab 100644 --- a/docs/plugins/lair.rst +++ b/docs/plugins/lair.rst @@ -11,7 +11,7 @@ Usage ----- ``lair`` - Mark the map as monster lair. + Mark the map as a monster lair. ``lair reset`` Mark the map as ordinary (not lair). diff --git a/docs/plugins/manipulator.rst b/docs/plugins/manipulator.rst index 3dc7f35b7..f333f3be0 100644 --- a/docs/plugins/manipulator.rst +++ b/docs/plugins/manipulator.rst @@ -102,7 +102,7 @@ and how many you are likely to need in a mature fort. These are just approximations. Your playstyle may demand more or fewer of each profession. - ``Chef`` (needed: 0, 3) - Buchery, Tanning, and Cooking. It is important to focus just a few dwarves + Butchery, Tanning, and Cooking. It is important to focus just a few dwarves on cooking since well-crafted meals make dwarves very happy. They are also an excellent trade good. - ``Craftsdwarf`` (needed: 0, 4-6) diff --git a/docs/plugins/map-render.rst b/docs/plugins/map-render.rst index efe3ed0c6..935fc1010 100644 --- a/docs/plugins/map-render.rst +++ b/docs/plugins/map-render.rst @@ -2,7 +2,7 @@ map-render ========== .. dfhack-tool:: - :summary: Provides a Lua API for rerendering portions of the map. + :summary: Provides a Lua API for re-rendering portions of the map. :tags: dev graphics :no-command: diff --git a/docs/plugins/power-meter.rst b/docs/plugins/power-meter.rst index 5e608ca46..701a656b5 100644 --- a/docs/plugins/power-meter.rst +++ b/docs/plugins/power-meter.rst @@ -2,7 +2,7 @@ power-meter =========== .. dfhack-tool:: - :summary: Allow presure plates to measure power. + :summary: Allow pressure plates to measure power. :tags: fort gameplay buildings :no-command: diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst index 1a3fc7ed9..5512a428b 100644 --- a/docs/plugins/regrass.rst +++ b/docs/plugins/regrass.rst @@ -2,7 +2,7 @@ regrass ======= .. dfhack-tool:: - :summary: Regrows all the grass. + :summary: Regrow all the grass. :tags: adventure fort armok animals map Use this command if your grazers have eaten everything down to the dirt. diff --git a/docs/plugins/rendermax.rst b/docs/plugins/rendermax.rst index 747b74642..ee909fd9a 100644 --- a/docs/plugins/rendermax.rst +++ b/docs/plugins/rendermax.rst @@ -12,7 +12,7 @@ Usage ----- ``rendermax light`` - Light the map tiles realisitically. Outside tiles are light during the day + Light the map tiles realistically. Outside tiles are light during the day and dark at night. Inside tiles are always dark unless a nearby unit is lighting it up, as if they were carrying torches. ``rendermax light sun |cycle`` diff --git a/docs/plugins/resume.rst b/docs/plugins/resume.rst index d6e0b81cb..af3fe161d 100644 --- a/docs/plugins/resume.rst +++ b/docs/plugins/resume.rst @@ -12,7 +12,7 @@ resume When enabled, this plugin will display a colored 'X' over suspended buildings. When run as a command, it can resume all suspended building jobs, allowing you to quickly recover if a bunch of jobs were suspended due to the workers getting -scared off by wildlife or items temporarily blocking buildling sites. +scared off by wildlife or items temporarily blocking building sites. Usage ----- diff --git a/docs/plugins/search.rst b/docs/plugins/search.rst index c489b4c40..e588e3196 100644 --- a/docs/plugins/search.rst +++ b/docs/plugins/search.rst @@ -9,7 +9,7 @@ search :no-command: Search options are added to the Stocks, Animals, Trading, Stockpile, Noble -aassignment candidates), Military (position candidates), Burrows (unit list), +assignment candidates), Military (position candidates), Burrows (unit list), Rooms, Announcements, Job List, and Unit List screens all get hotkeys that allow you to dynamically filter the displayed lists. diff --git a/docs/plugins/seedwatch.rst b/docs/plugins/seedwatch.rst index c471f467d..b41f3a977 100644 --- a/docs/plugins/seedwatch.rst +++ b/docs/plugins/seedwatch.rst @@ -21,7 +21,7 @@ Usage Start managing seed and plant cooking. By default, no types are watched. You have to add them with further ``seedwatch`` commands. ``seedwatch `` - Adds the specifiied type to the watchlist (if it's not already there) and + Adds the specified type to the watchlist (if it's not already there) and sets the target number of seeds to the specified number. You can pass the keyword ``all`` instead of a specific type to set the target for all types. ``seedwatch `` diff --git a/docs/plugins/stocks.rst b/docs/plugins/stocks.rst index d8fae9579..03eb3a72c 100644 --- a/docs/plugins/stocks.rst +++ b/docs/plugins/stocks.rst @@ -21,4 +21,4 @@ Usage stocks show Running ``stocks show`` will bring you to the fortress-wide stock management -screen from whereever you are. +screen from wherever you are. diff --git a/docs/plugins/tiletypes.rst b/docs/plugins/tiletypes.rst index 44fba00df..7b93dd6de 100644 --- a/docs/plugins/tiletypes.rst +++ b/docs/plugins/tiletypes.rst @@ -58,7 +58,7 @@ Examples ``tiletypes-command filter material STONE ; f shape WALL ; paint shape FLOOR`` Turn all stone walls into floors, preserving the material. ``tiletypes-command p any ; p s wall ; p sp normal`` - Clear the paint specificaiton and set it to unsmoothed walls. + Clear the paint specification and set it to unsmoothed walls. ``tiletypes-command f any ; p stone marble ; p sh wall ; p sp normal ; r 10 10`` Prepare to paint a 10x10 area of marble walls, ready for harvesting for flux. diff --git a/docs/plugins/tubefill.rst b/docs/plugins/tubefill.rst index 1085897c8..80282f6d9 100644 --- a/docs/plugins/tubefill.rst +++ b/docs/plugins/tubefill.rst @@ -2,7 +2,7 @@ tubefill ======== .. dfhack-tool:: - :summary: Replentishes mined-out adamantine. + :summary: Replenishes mined-out adamantine. :tags: fort armok map Veins that were originally hollow will be left alone. diff --git a/docs/plugins/tweak.rst b/docs/plugins/tweak.rst index bb125fc0d..aa1ad578b 100644 --- a/docs/plugins/tweak.rst +++ b/docs/plugins/tweak.rst @@ -66,7 +66,7 @@ Commands that persist until disabled or DF quits: ``civ-view-agreement`` Fixes overlapping text on the "view agreement" screen. ``condition-material`` - Fixes a crash in the work order contition material list (:bug:`9905`). + Fixes a crash in the work order condition material list (:bug:`9905`). ``craft-age-wear`` Fixes crafted items not wearing out over time (:bug:`6003`). With this tweak, items made from cloth and leather will gain a level of wear every 20 diff --git a/docs/plugins/workflow.rst b/docs/plugins/workflow.rst index 9013ba189..f085a40da 100644 --- a/docs/plugins/workflow.rst +++ b/docs/plugins/workflow.rst @@ -122,7 +122,7 @@ The constraint spec consists of 4 parts, separated with ``/`` characters:: ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,] The first part is mandatory and specifies the item type and subtype, using the -raw tokens for items (the same syntax used custom reaction inputs). For more +raw tokens for items (the same syntax used for custom reaction inputs). For more information, see :wiki:`this wiki page `. The subsequent parts are optional: diff --git a/docs/plugins/zone.rst b/docs/plugins/zone.rst index 93b244399..0f77e1d3e 100644 --- a/docs/plugins/zone.rst +++ b/docs/plugins/zone.rst @@ -74,8 +74,8 @@ Filters :all: Process all units. :count : Process only up to n units. :unassigned: Not assigned to zone, chain or built cage. -:minage : Minimum age. Must be followed by number. -:maxage : Maximum age. Must be followed by number. +:minage : Minimum age. Must be followed by a number. +:maxage : Maximum age. Must be followed by a number. :not: Negates the next filter keyword. All of the keywords documented below are negatable. :race: Must be followed by a race RAW ID (e.g. BIRD_TURKEY, ALPACA, From 663916b86bbc22ed4885352591c6da32de20c487 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 28 Sep 2022 14:56:20 -0700 Subject: [PATCH 729/854] update wording for autolabor --- docs/plugins/autolabor.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/plugins/autolabor.rst b/docs/plugins/autolabor.rst index b39884957..63b830dfe 100644 --- a/docs/plugins/autolabor.rst +++ b/docs/plugins/autolabor.rst @@ -46,10 +46,9 @@ Hunting is never assigned without a butchery, and fishing is never assigned without a fishery. For each labor, a preference order is calculated based on skill, excluding those -who can't do the job. Dwarves who are masters of particular skills are -deprioritized in the preference list for other skills. The labor is then added -to the best dwarves for that labor, then to additional dwarfs that -meet any of these conditions: +who can't do the job. Dwarves who are masters of a skill are deprioritized for +other skills. The labor is then added to the best dwarves for that +labor, then to additional dwarfs that meet any of these conditions: * The dwarf is idle and there are no idle dwarves assigned to this labor * The dwarf has non-zero skill associated with the labor From dbeaff7c16e92710b34ee4a5eca2e5f65228dab1 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 28 Sep 2022 21:57:33 +0000 Subject: [PATCH 730/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 4f568fc83..3da6fa744 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 4f568fc8331d62bc6646055c1252bba19b3a526d +Subproject commit 3da6fa7444ad537e92a6ad2e1cfad964990c87fd From 95bbdcd93474d1b55816edb2da93a2a543e1749d Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 29 Sep 2022 07:37:17 +0000 Subject: [PATCH 731/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 3da6fa744..cfb11e1d0 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 3da6fa7444ad537e92a6ad2e1cfad964990c87fd +Subproject commit cfb11e1d0cee48f29b4a8323acc7a32787a8ddda From 8a381ee520d7fa4e17005fc43c1be4403e633141 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 30 Sep 2022 07:42:02 +0000 Subject: [PATCH 732/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index cfb11e1d0..19c0d1d8b 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit cfb11e1d0cee48f29b4a8323acc7a32787a8ddda +Subproject commit 19c0d1d8b131446c27513eb85bd27d48e8ea214e From 3f6e92eda49579a7fa12b589d7a794448c5dc5bd Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 30 Sep 2022 11:58:22 -0700 Subject: [PATCH 733/854] finalize 0.47.05-r7 release --- CMakeLists.txt | 2 +- docs/changelog.txt | 16 ++++++++++++++-- library/xml | 2 +- scripts | 2 +- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8aa57d574..2e6f5c835 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,7 +192,7 @@ endif() # set up versioning. set(DF_VERSION "0.47.05") -set(DFHACK_RELEASE "r6") +set(DFHACK_RELEASE "r7") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/docs/changelog.txt b/docs/changelog.txt index 52e906486..72fb5906c 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -33,13 +33,25 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future +## New Plugins + +## Fixes + +## Misc Improvements + +## Documentation + +## API + +## Lua + +# 0.47.05-r7 + ## New Plugins - `autonestbox`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autonestbox start`` to ``enable autonestbox``. - `autobutcher`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autobutcher start`` to ``enable autobutcher``. - `overlay`: display a "DFHack" button in the lower left corner that you can click to start the new GUI command launcher. The `dwarfmonitor` weather display had to be moved to make room for the button. If you are seeing the weather indicator rendered over the overlay button, please remove the ``dfhack-config/dwarfmonitor.json`` file to fix the weather indicator display offset. -## New Tweaks - ## New Internal Commands - `tags`: new built-in command to list the tool category tags and their definitions. tags associated with each tool are visible in the tool help and in the output of `ls`. diff --git a/library/xml b/library/xml index f5fab13fb..f5019a5c6 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f5fab13fb652dd953e9d59e8deca032b26131208 +Subproject commit f5019a5c6f19ef05a28bd974c3e8668b78e6e2a4 diff --git a/scripts b/scripts index 19c0d1d8b..04836f16f 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 19c0d1d8b131446c27513eb85bd27d48e8ea214e +Subproject commit 04836f16f9705bfbb1438fc0870b3e41b6539f75 From 8e9f3e902f889f38764fb5e86950d12fac2badd5 Mon Sep 17 00:00:00 2001 From: Myk Date: Sat, 1 Oct 2022 16:59:33 -0700 Subject: [PATCH 734/854] Update quickfort-user-guide.rst Fix typo --- docs/guides/quickfort-user-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 71a80818c..6f606c43a 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -190,8 +190,8 @@ dug-out area:: Cw Cw Cw # # # # # # -Note my generosity -- in addition to the bed (:kbd:`b`) I've built a chest -(:kbd:`c`) here for the dwarf as well. You must use the full series of keys +Note my generosity -- in addition to the bed (:kbd:`b`) I've built a container +(:kbd:`h`) here for the dwarf as well. You must use the full series of keys needed to build something in each cell, e.g. :kbd:`C`:kbd:`w` indicates we should enter DF's constructions submenu (:kbd:`C`) and select walls (:kbd:`w`). From 55d2c20307f3faf293362cc12ef808d92b0fe6bd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 21:37:36 +0000 Subject: [PATCH 735/854] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/python-jsonschema/check-jsonschema: 0.18.2 → 0.18.3](https://github.com/python-jsonschema/check-jsonschema/compare/0.18.2...0.18.3) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 25f043624..00e01ea22 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.18.2 + rev: 0.18.3 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks From 739792a59e0978bf39995c543d3bc33cb85d0a69 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 4 Oct 2022 07:30:12 +0000 Subject: [PATCH 736/854] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index f5019a5c6..f2b59b8d5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f5019a5c6f19ef05a28bd974c3e8668b78e6e2a4 +Subproject commit f2b59b8d5036cca90a88245d0d7c8b2a713f60ee diff --git a/scripts b/scripts index 04836f16f..41bfa9005 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 04836f16f9705bfbb1438fc0870b3e41b6539f75 +Subproject commit 41bfa9005ec78094388518d40e7aaa43f7d35890 From dc535004e9670db733f6d65f89f20215750c29c4 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 5 Oct 2022 12:51:30 -0700 Subject: [PATCH 737/854] better formatting for ls output for tags --- docs/changelog.txt | 1 + library/lua/helpdb.lua | 6 ++++-- test/library/helpdb.lua | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 72fb5906c..d00f4c246 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -38,6 +38,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes ## Misc Improvements +- `ls`: indent tag listings and wrap them in the right column for better readability ## Documentation diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index ac6adb55d..d07445399 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -726,13 +726,15 @@ end local function list_entries(skip_tags, include, exclude) local entries = search_entries(include, exclude) for _,entry in ipairs(entries) do - print_columns(entry, get_entry_short_help(entry)) + local short_help = get_entry_short_help(entry) if not skip_tags then local tags = set_to_sorted_list(get_entry_tags(entry)) if #tags > 0 then - print((' tags: %s'):format(table.concat(tags, ', '))) + local taglist = table.concat(tags, ', ') + short_help = short_help .. NEWLINE .. 'tags: ' .. taglist end end + print_columns(entry, short_help) end if #entries == 0 then print('No matches.') diff --git a/test/library/helpdb.lua b/test/library/helpdb.lua index 067f35532..efa9e93d5 100644 --- a/test/library/helpdb.lua +++ b/test/library/helpdb.lua @@ -637,7 +637,7 @@ function test.ls() expect.eq(5, mock_print.call_count) expect.eq('inscript_docs in-file short description for inscript_docs.', mock_print.call_args[1][1]) - expect.eq(' tags: map', mock_print.call_args[2][1]) + expect.eq(' tags: map', mock_print.call_args[2][1]) expect.eq('nodoc_command cpp description.', mock_print.call_args[3][1]) expect.eq('nodocs_samename Nodocs samename.', @@ -652,15 +652,15 @@ function test.ls() expect.eq(6, mock_print.call_count) expect.eq('bindboxers Bind your boxers.', mock_print.call_args[1][1]) - expect.eq(' tags: armok, fort, units', + expect.eq(' tags: armok, fort, units', mock_print.call_args[2][1]) expect.eq('boxbinders Box your binders.', mock_print.call_args[3][1]) - expect.eq(' tags: armok, fort, units', + expect.eq(' tags: armok, fort, units', mock_print.call_args[4][1]) expect.eq('samename Samename.', mock_print.call_args[5][1]) - expect.eq(' tags: armok, fort, units', + expect.eq(' tags: armok, fort, units', mock_print.call_args[6][1]) end) From 9817106c30ee63527c22e00833692c5b009dc7d0 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 5 Oct 2022 13:30:14 -0700 Subject: [PATCH 738/854] add --exclude option for ls --- docs/Lua API.rst | 11 ++++-- docs/builtins/ls.rst | 2 ++ docs/changelog.txt | 1 + library/Core.cpp | 13 +++++-- library/lua/helpdb.lua | 60 ++++++++++++++++++++++++------- test/library/helpdb.lua | 78 ++++++++++++++++++++++++++++++++++------- 6 files changed, 136 insertions(+), 29 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 4aa8d78cb..d8a76dd28 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3150,8 +3150,8 @@ Each entry has several properties associated with it: alphabetized by their last path component, with populated path components coming before null path components (e.g. ``autobutcher`` will immediately follow ``gui/autobutcher``). - The optional ``include`` and ``exclude`` filter params are maps with the - following elements: + The optional ``include`` and ``exclude`` filter params are maps (or lists of + maps) with the following elements: :str: if a string, filters by the given substring. if a table of strings, includes entry names that match any of the given substrings. @@ -3160,6 +3160,13 @@ Each entry has several properties associated with it: :entry_type: if a string, matches entries of the given type. if a table of strings, includes entries that match any of the given types. + Elements in a map are ANDed together (e.g. if both ``str`` and ``tag`` are + specified, the match is on any of the ``str`` elements AND any of the ``tag`` + elements). + + If lists of filters are passed instead of a single map, the maps are ORed + (that is, the match succeeds if any of the filters match). + If ``include`` is ``nil`` or empty, then all entries are included. If ``exclude`` is ``nil`` or empty, then no entries are filtered out. diff --git a/docs/builtins/ls.rst b/docs/builtins/ls.rst index 7305a0256..cd6bc4126 100644 --- a/docs/builtins/ls.rst +++ b/docs/builtins/ls.rst @@ -40,3 +40,5 @@ Options Don't print out the tags associated with each command. ``--dev`` Include commands intended for developers and modders. +``--exclude [,...]`` + Exclude commands that match any of the given strings. diff --git a/docs/changelog.txt b/docs/changelog.txt index d00f4c246..fb11def8e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -39,6 +39,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - `ls`: indent tag listings and wrap them in the right column for better readability +- `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output. ## Documentation diff --git a/library/Core.cpp b/library/Core.cpp index 87f78f56c..73336b2e7 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -622,12 +622,18 @@ void ls_helper(color_ostream &con, const vector ¶ms) { vector filter; bool skip_tags = false; bool show_dev_commands = false; + string exclude_strs = ""; + bool in_exclude = false; for (auto str : params) { - if (str == "--notags") + if (in_exclude) + exclude_strs = str; + else if (str == "--notags") skip_tags = true; else if (str == "--dev") show_dev_commands = true; + else if (str == "--exclude") + in_exclude = true; else filter.push_back(str); } @@ -636,7 +642,7 @@ void ls_helper(color_ostream &con, const vector ¶ms) { auto L = Lua::Core::State; Lua::StackUnwinder top(L); - if (!lua_checkstack(L, 4) || + if (!lua_checkstack(L, 5) || !Lua::PushModulePublic(con, L, "helpdb", "ls")) { con.printerr("Failed to load helpdb Lua code\n"); return; @@ -645,8 +651,9 @@ void ls_helper(color_ostream &con, const vector ¶ms) { Lua::PushVector(L, filter); Lua::Push(L, skip_tags); Lua::Push(L, show_dev_commands); + Lua::Push(L, exclude_strs); - if (!Lua::SafeCall(con, L, 3, 0)) { + if (!Lua::SafeCall(con, L, 4, 0)) { con.printerr("Failed Lua call to helpdb.ls.\n"); } } diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index d07445399..5af41c929 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -14,6 +14,8 @@ local _ENV = mkmodule('helpdb') +local argparse = require('argparse') + local MAX_STALE_MS = 60000 -- paths @@ -588,6 +590,8 @@ function sort_by_basename(a, b) return false end +-- returns true if all filter elements are matched (i.e. any of the tags AND +-- any of the strings AND any of the entry_types) local function matches(entry_name, filter) if filter.tag then local matched = false @@ -630,9 +634,18 @@ local function matches(entry_name, filter) return true end +local function matches_any(entry_name, filters) + for _,filter in ipairs(filters) do + if matches(entry_name, filter) then + return true + end + end + return false +end + -- normalizes the lists in the filter and returns nil if no filter elements are -- populated -local function normalize_filter(f) +local function normalize_filter_map(f) if not f then return nil end local filter = {} filter.str = normalize_string_list(f.str) @@ -644,11 +657,21 @@ local function normalize_filter(f) return filter end +local function normalize_filter_list(fs) + if not fs then return nil end + local filter_list = {} + for _,f in ipairs(#fs > 0 and fs or {fs}) do + table.insert(filter_list, normalize_filter_map(f)) + end + if #filter_list == 0 then return nil end + return filter_list +end + -- returns a list of entry names, alphabetized by their last path component, -- with populated path components coming before null path components (e.g. -- autobutcher will immediately follow gui/autobutcher). --- the optional include and exclude filter params are maps with the following --- elements: +-- the optional include and exclude filter params are maps (or lists of maps) +-- with the following elements: -- str - if a string, filters by the given substring. if a table of strings, -- includes entry names that match any of the given substrings. -- tag - if a string, filters by the given tag name. if a table of strings, @@ -658,14 +681,18 @@ end -- types are: "builtin", "plugin", "command". note that many plugin -- commands have the same name as the plugin, so those entries will -- match both "plugin" and "command" types. +-- filter elements in a map are ANDed together (e.g. if both str and tag are +-- specified, the match is on any of the str elements AND any of the tag +-- elements). If lists of maps are passed, the maps are ORed (that is, the match +-- succeeds if any of the filters match). function search_entries(include, exclude) ensure_db() - include = normalize_filter(include) - exclude = normalize_filter(exclude) + include = normalize_filter_list(include) + exclude = normalize_filter_list(exclude) local entries = {} for entry in pairs(entrydb) do - if (not include or matches(entry, include)) and - (not exclude or not matches(entry, exclude)) then + if (not include or matches_any(entry, include)) and + (not exclude or not matches_any(entry, exclude)) then table.insert(entries, entry) end end @@ -743,21 +770,30 @@ end -- wraps the list_entries() API to provide a more convenient interface for Core -- to implement the 'ls' builtin command. --- filter_str - if a tag name, will filter by that tag. otherwise, will filter --- as a substring +-- filter_str - if a tag name (or a list of tag names), will filter by that +-- tag/those tags. otherwise, will filter as a substring/list of +-- substrings -- skip_tags - whether to skip printing tag info -- show_dev_commands - if true, will include scripts in the modtools/ and -- devel/ directories. otherwise those scripts will be -- excluded -function ls(filter_str, skip_tags, show_dev_commands) +-- exclude_strs - comma-separated list of strings. entries are excluded if +-- they match any of the strings. +function ls(filter_str, skip_tags, show_dev_commands, exclude_strs) local include = {entry_type={ENTRY_TYPES.COMMAND}} if is_tag(filter_str) then include.tag = filter_str else include.str = filter_str end - list_entries(skip_tags, include, - show_dev_commands and {} or {tag='dev'}) + local excludes = {} + if exclude_strs and #exclude_strs > 0 then + table.insert(excludes, {str=argparse.stringList(exclude_strs)}) + end + if not show_dev_commands then + table.insert(excludes, {tag='dev'}) + end + list_entries(skip_tags, include, excludes) end local function list_tags() diff --git a/test/library/helpdb.lua b/test/library/helpdb.lua index efa9e93d5..1f1e58ba9 100644 --- a/test/library/helpdb.lua +++ b/test/library/helpdb.lua @@ -36,6 +36,7 @@ local mock_script_db = { inscript_docs=true, inscript_short_only=true, nodocs_script=true, + dev_script=true, } local files = { @@ -48,6 +49,8 @@ local files = { * units: Tools that interact with units. +* dev: Dev tools. + * nomembers: Nothing is tagged with this. ]], ['hack/docs/docs/tools/hascommands.txt']=[[ @@ -113,6 +116,20 @@ Command: "subdir/scriptname" Documented subdir/scriptname. Documented full help. + ]], + ['hack/docs/docs/tools/dev_script.txt']=[[ +dev_script +========== + +Tags: dev + +Command: "dev_script" + + Short desc. + +Full help. +]====] +script contents ]], ['scripts/scriptpath/basic.lua']=[[ -- in-file short description for basic @@ -216,6 +233,9 @@ Command: "inscript_docs" Documented full help. ]====] +script contents + ]], + ['other/scriptpath/dev_script.lua']=[[ script contents ]], } @@ -495,7 +515,7 @@ function test.is_tag() end function test.get_tags() - expect.table_eq({'armok', 'fort', 'map', 'nomembers', 'units'}, + expect.table_eq({'armok', 'dev', 'fort', 'map', 'nomembers', 'units'}, h.get_tags()) end @@ -534,8 +554,8 @@ end function test.search_entries() -- all entries, in alphabetical order by last path component local expected = {'?', 'alias', 'basic', 'bindboxers', 'boxbinders', - 'clear', 'cls', 'die', 'dir', 'disable', 'devel/dump-rpc', 'enable', - 'fpause', 'hascommands', 'help', 'hide', 'inscript_docs', + 'clear', 'cls', 'dev_script', 'die', 'dir', 'disable', 'devel/dump-rpc', + 'enable', 'fpause', 'hascommands', 'help', 'hide', 'inscript_docs', 'inscript_short_only', 'keybinding', 'kill-lua', 'load', 'ls', 'man', 'nocommand', 'nodoc_command', 'nodocs_hascommands', 'nodocs_nocommand', 'nodocs_samename', 'nodocs_script', 'plug', 'reload', 'samename', @@ -555,19 +575,26 @@ function test.search_entries() expect.table_eq(expected, h.search_entries({str='script', entry_type='builtin'})) - expected = {'inscript_docs', 'inscript_short_only','nodocs_script', - 'subdir/scriptname'} + expected = {'dev_script', 'inscript_docs', 'inscript_short_only', + 'nodocs_script', 'subdir/scriptname'} expect.table_eq(expected, h.search_entries({str='script'}, {entry_type='builtin'})) expected = {'bindboxers', 'boxbinders'} expect.table_eq(expected, h.search_entries({str='box'})) + + expected = {'bindboxers', 'boxbinders', 'inscript_docs', + 'inscript_short_only', 'nodocs_script', 'subdir/scriptname'} + expect.table_eq(expected, h.search_entries({{str='script'}, {str='box'}}, + {{entry_type='builtin'}, + {tag='dev'}}), + 'multiple filters for include and exclude') end function test.get_commands() local expected = {'?', 'alias', 'basic', 'bindboxers', 'boxbinders', - 'clear', 'cls', 'die', 'dir', 'disable', 'devel/dump-rpc', 'enable', - 'fpause', 'help', 'hide', 'inscript_docs', 'inscript_short_only', + 'clear', 'cls', 'dev_script', 'die', 'dir', 'disable', 'devel/dump-rpc', + 'enable', 'fpause', 'help', 'hide', 'inscript_docs', 'inscript_short_only', 'keybinding', 'kill-lua', 'load', 'ls', 'man', 'nodoc_command', 'nodocs_samename', 'nodocs_script', 'plug', 'reload', 'samename', 'script', 'subdir/scriptname', 'sc-script', 'show', 'tags', 'type', @@ -598,21 +625,23 @@ function test.tags() local mock_print = mock.func() mock.patch(h, 'print', mock_print, function() h.tags() - expect.eq(7, mock_print.call_count) + expect.eq(8, mock_print.call_count) expect.eq('armok Tools that give you complete control over an aspect of the', mock_print.call_args[1][1]) expect.eq(' game or provide access to information that the game', mock_print.call_args[2][1]) expect.eq(' intentionally keeps hidden.', mock_print.call_args[3][1]) - expect.eq('fort Tools that are useful while in fort mode.', + expect.eq('dev Dev tools.', mock_print.call_args[4][1]) - expect.eq('map Tools that interact with the game map.', + expect.eq('fort Tools that are useful while in fort mode.', mock_print.call_args[5][1]) - expect.eq('nomembers Nothing is tagged with this.', + expect.eq('map Tools that interact with the game map.', mock_print.call_args[6][1]) - expect.eq('units Tools that interact with units.', + expect.eq('nomembers Nothing is tagged with this.', mock_print.call_args[7][1]) + expect.eq('units Tools that interact with units.', + mock_print.call_args[8][1]) end) end @@ -670,4 +699,29 @@ function test.ls() expect.eq(1, mock_print.call_count) expect.eq('No matches.', mock_print.call_args[1][1]) end) + + -- test skipping tags and excluding strings + mock_print = mock.func() + mock.patch(h, 'print', mock_print, function() + h.ls('armok', true, false, 'boxer,binder') + expect.eq(1, mock_print.call_count) + expect.eq('samename Samename.', mock_print.call_args[1][1]) + end) + + -- test excluding dev scripts + mock_print = mock.func() + mock.patch(h, 'print', mock_print, function() + h.ls('_script', true, false, 'inscript,nodocs') + expect.eq(1, mock_print.call_count) + expect.eq('No matches.', mock_print.call_args[1][1]) + end) + + -- test including dev scripts + mock_print = mock.func() + mock.patch(h, 'print', mock_print, function() + h.ls('_script', true, true, 'inscript,nodocs') + expect.eq(1, mock_print.call_count) + expect.eq('dev_script Short desc.', + mock_print.call_args[1][1]) + end) end From 33816b8bc215dcf7b11c9c6ea1cc98f9e80ce0e0 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 5 Oct 2022 14:01:09 -0700 Subject: [PATCH 739/854] optionally process only the cur z-level and below --- docs/changelog.txt | 1 + docs/plugins/dig.rst | 12 ++++++++---- plugins/dig.cpp | 31 ++++++++++++------------------- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index fb11def8e..eba15cfb4 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -40,6 +40,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - `ls`: indent tag listings and wrap them in the right column for better readability - `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output. +- `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down ## Documentation diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst index 4384ade65..a10db97df 100644 --- a/docs/plugins/dig.rst +++ b/docs/plugins/dig.rst @@ -25,7 +25,7 @@ dig :summary: Designate circles. .. dfhack-command:: digtype - :summary: Designate all vein tiles of the selected type. + :summary: Designate all vein tiles of the same type as the selected tile. .. dfhack-command:: digexp :summary: Designate dig patterns for exploratory mining. @@ -50,9 +50,9 @@ Usage Designate circles. The diameter is the number of tiles across the center of the circle that you want to dig. See the `digcircle`_ section below for options. -``digtype [] [-p]`` - Designate all vein tiles of the selected type. See the `digtype`_ section - below for options. +``digtype [] [-p] [-z]`` + Designate all vein tiles of the same type as the selected tile. See the + `digtype`_ section below for options. ``digexp [] [] [-p]`` Designate dig patterns for exploratory mining. See the `digexp`_ section below for options. @@ -143,6 +143,10 @@ Designation options: ``clear`` Clear any designations. +You can also pass a ``-z`` option, which restricts designations to the current +z-level and down. This is useful when you don't want to designate tiles on the +same z-levels as your carefully dug fort above. + digexp ------ diff --git a/plugins/dig.cpp b/plugins/dig.cpp index ddc2cd97e..945bf3613 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -35,6 +35,7 @@ command_result digtype (color_ostream &out, vector & parameters); DFHACK_PLUGIN("dig"); REQUIRE_GLOBAL(ui_sidebar_menus); REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(window_z); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { @@ -1417,16 +1418,18 @@ command_result digtype (color_ostream &out, vector & parameters) //mostly copy-pasted from digv int32_t priority = parse_priority(out, parameters); CoreSuspender suspend; - if ( parameters.size() > 1 ) + + if (!Maps::IsValid()) { - out.printerr("Too many parameters.\n"); + out.printerr("Map is not available!\n"); return CR_FAILURE; } - int32_t targetDigType; - if ( parameters.size() == 1 ) - { - string parameter = parameters[0]; + uint32_t xMax,yMax,zMax; + Maps::getSize(xMax,yMax,zMax); + + int32_t targetDigType = -1; + for (string parameter : parameters) { if ( parameter == "clear" ) targetDigType = tile_dig_designation::No; else if ( parameter == "dig" ) @@ -1441,26 +1444,16 @@ command_result digtype (color_ostream &out, vector & parameters) targetDigType = tile_dig_designation::DownStair; else if ( parameter == "up" ) targetDigType = tile_dig_designation::UpStair; + else if ( parameter == "-z" ) + zMax = *window_z + 1; else { - out.printerr("Invalid parameter.\n"); + out.printerr("Invalid parameter: '%s'.\n", parameter.c_str()); return CR_FAILURE; } } - else - { - targetDigType = -1; - } - - if (!Maps::IsValid()) - { - out.printerr("Map is not available!\n"); - return CR_FAILURE; - } int32_t cx, cy, cz; - uint32_t xMax,yMax,zMax; - Maps::getSize(xMax,yMax,zMax); uint32_t tileXMax = xMax * 16; uint32_t tileYMax = yMax * 16; Gui::getCursorCoords(cx,cy,cz); From 24232e894a17e6b6d293ec8413d37929d266a311 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 6 Oct 2022 11:13:16 -0700 Subject: [PATCH 740/854] create Scrollbar widget and integrate with List --- docs/Lua API.rst | 41 ++++++++--- docs/changelog.txt | 2 + library/lua/gui/widgets.lua | 138 +++++++++++++++++++++++++++++++++--- 3 files changed, 165 insertions(+), 16 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index d8a76dd28..b9c09c673 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4032,6 +4032,38 @@ following keyboard hotkeys: - Ctrl-Left/Right arrow: move the cursor one word to the left or right. - Alt-Left/Right arrow: move the cursor to the beginning/end of the text. +Scrollbar class +--------------- + +This Widget subclass implements mouse-interactive scrollbars whose bar sizes +represent the amount of content currently visible in an associated display +widget (like a `Label class`_ or a `List class`_). By default they are styled +like scrollbars used in the vanilla DF help screens, but they are configurable. + +Scrollbars have the following attributes: + +:fg: Specifies the pen for the scroll icons and the active part of the bar. Default is ``COLOR_LIGHTGREEN``. +:bg: Specifies the pen for the background part of the scrollbar. Default is ``COLOR_CYAN``. +:on_scroll: A callback called when the scrollbar is scrolled. It will be called with a single string parameter with a value of "up_large", "down_large", "up_small", or "down_small". + +The Scrollbar widget implements the following methods: + +* ``scrollbar:update(top_elem, elems_per_page, num_elems)`` + + Updates the info about the widget that the scrollbar is paired with. + The ``top_elem`` param is the (one-based) index of the first visible element. + The ``elems_per_page`` param is the maximum number of elements that can be + shown at one time. The ``num_elems`` param is the total number of elements + that the paried widget can scroll through. The scrollbar will adjust its + scrollbar size and position accordingly. + +Clicking on the arrows at the top or the bottom of a scrollbar will scroll an +associated widget by a small amount. Clicking on the unfilled portion of the +scrollbar above or below the filled area will scroll by a larger amount in that +direction. The amount of scrolling done in each case in determined by the +associated widget, and after scrolling is complete, the associated widget must +call ``scrollbar:update()`` with updated new display info. + Label class ----------- @@ -4056,13 +4088,7 @@ It has the following attributes: icons next to the text in an additional column (``frame_inset`` is adjusted to have ``.r`` or ``.l`` greater than ``0``), ``nil`` same as ``'right'`` but changes ``frame_inset`` only if a scroll icon is actually necessary (if ``getTextHeight()`` is greater than ``frame_body.height``). Default is ``nil``. -:scrollbar_fg: Specifies the pen for the scroll icons and the active part of the bar. Default is ``COLOR_LIGHTGREEN`` (the same as the native DF help screens). -:scrollbar_bg: Specifies the pen for the background part of the scrollbar. Default is ``COLOR_CYAN`` (the same as the native DF help screens). - -If the scrollbar is shown, it will react to mouse clicks on the scrollbar itself. -Clicking on the arrows at the top or the bottom will scroll by one line, and -clicking on the unfilled portion of the scrollbar will scroll by a half page in -that direction. +:scrollbar: The table of attributes to pass to the `Scrollbar class`_. The text itself is represented as a complex structure, and passed to the object via the ``text`` argument of the constructor, or via @@ -4283,7 +4309,6 @@ Every list item may be specified either as a string, or as a lua table with the following fields: :text: Specifies the label text in the same format as the Label text. -:caption, [1]: Deprecated legacy aliases for **text**. :text_*: Reserved for internal use. :key: Specifies a keybinding that acts as a shortcut for the specified item. :icon: Specifies an icon string, or a pen to paint a single character. May be a callback. diff --git a/docs/changelog.txt b/docs/changelog.txt index eba15cfb4..b1d5300a9 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,12 +41,14 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `ls`: indent tag listings and wrap them in the right column for better readability - `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output. - `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down +- UX: List widgets now have mouse-interactive scrollbars ## Documentation ## API ## Lua +- ``widgets.Scrollbar``: new scrollbar widget that can be paired with an associated scrollable widget. Integrated with ``widgets.Label`` and ``widgets.List``. # 0.47.05-r7 diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 7076570b9..20aff7aa4 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -359,6 +359,96 @@ function EditField:onInput(keys) return self.modal end +--------------- +-- Scrollbar -- +--------------- + +Scrollbar = defclass(Scrollbar, Widget) + +Scrollbar.ATTRS{ + fg = COLOR_LIGHTGREEN, + bg = COLOR_CYAN, + on_scroll = DEFAULT_NIL, +} + +function Scrollbar:preinit(init_table) + init_table.frame = init_table.frame or {} + init_table.frame.w = init_table.frame.w or 1 +end + +function Scrollbar:init() + self:update(1, 1, 1) +end + +-- calculate and cache the number of tiles of empty space above the top of the +-- scrollbar and the number of tiles the scrollbar should occupy to represent +-- the percentage of text that is on the screen. +-- if elems_per_page or num_elems are not specified, the last values passed to +-- Scrollbar:update() are used. +function Scrollbar:update(top_elem, elems_per_page, num_elems) + if not top_elem then error('must specify index of new top element') end + elems_per_page = elems_per_page or self.elems_per_page + num_elems = num_elems or self.num_elems + + local frame_height = self.frame_body and self.frame_body.height or 3 + local scrollbar_body_height = frame_height - 2 + local height = math.max(1, math.floor( + (math.min(elems_per_page, num_elems) * scrollbar_body_height) / + num_elems)) + + local max_pos = scrollbar_body_height - height + local pos = math.ceil(((top_elem-1) * max_pos) / + (num_elems - elems_per_page)) + + self.top_elem = top_elem + self.elems_per_page, self.num_elems = elems_per_page, num_elems + self.bar_offset, self.bar_height = pos, height +end + +local UP_ARROW_CHAR = string.char(24) +local DOWN_ARROW_CHAR = string.char(25) +local NO_ARROW_CHAR = string.char(32) +local BAR_CHAR = string.char(7) +local BAR_BG_CHAR = string.char(179) + +function Scrollbar:onRenderBody(dc) + -- don't draw if all elements are visible + if self.elems_per_page >= self.num_elems then return end + -- render up arrow if we're not at the top + dc:seek(0, 0):char( + self.top_elem == 1 and NO_ARROW_CHAR or UP_ARROW_CHAR, self.fg, self.bg) + -- render scrollbar body + local starty = self.bar_offset + 1 + local endy = self.bar_offset + self.bar_height + for y=1,dc.height-2 do + dc:seek(0, y) + if y >= starty and y <= endy then + dc:char(BAR_CHAR, self.fg) + else + dc:char(BAR_BG_CHAR, self.bg) + end + end + -- render down arrow if we're not at the bottom + local last_visible_el = self.top_elem + self.elems_per_page - 1 + dc:seek(0, dc.height-1):char( + last_visible_el >= self.num_elems and NO_ARROW_CHAR or DOWN_ARROW_CHAR, + self.fg, self.bg) +end + +function Scrollbar:onInput(keys) + if not keys._MOUSE_L_DOWN or not self.on_scroll then return false end + local _,y = self:getMousePos() + if not y then return false end + local scroll = nil + if y == 0 then scroll = 'up_small' + elseif y == self.frame_body.height - 1 then scroll = 'down_small' + elseif y <= self.bar_offset then scroll = 'up_large' + elseif y > self.bar_offset + self.bar_height then scroll = 'down_large' + end + if scroll then self.on_scroll(scroll) end + return true +end + ----------- -- Label -- ----------- @@ -610,12 +700,6 @@ local function get_scrollbar_pos_and_height(label) return pos, height end -local UP_ARROW_CHAR = string.char(24) -local DOWN_ARROW_CHAR = string.char(25) -local NO_ARROW_CHAR = string.char(32) -local BAR_CHAR = string.char(7) -local BAR_BG_CHAR = string.char(179) - function Label:render_scrollbar(dc, x, y1, y2) -- render up arrow if we're not at the top dc:seek(x, y1):char( @@ -953,6 +1037,11 @@ List.ATTRS{ function List:init(info) self.page_top = 1 self.page_size = 1 + self.scrollbar = Scrollbar{ + frame={r=0}, + on_scroll=self:callback('on_scrollbar')} + + self:addviews{self.scrollbar} if info.choices then self:setChoices(info.choices, info.selected) @@ -1017,13 +1106,17 @@ function List:postComputeFrame(body) self:moveCursor(0) end +local function update_list_scrollbar(list) + self.scrollbar:update(list.page_top, list.page_size, #list.choices) +end + function List:moveCursor(delta, force_cb) - local page = math.max(1, self.page_size) local cnt = #self.choices if cnt < 1 then self.page_top = 1 self.selected = 1 + update_list_scrollbar(self) if force_cb and self.on_select then self.on_select(nil,nil) end @@ -1046,14 +1139,40 @@ function List:moveCursor(delta, force_cb) end end + local buffer = 1 + math.min(4, math.floor(self.page_size/10)) + self.selected = 1 + off % cnt - self.page_top = 1 + page * math.floor((self.selected-1) / page) + if (self.selected - buffer) < self.page_top then + self.page_top = math.max(1, self.selected - buffer) + elseif (self.selected + buffer + 1) > (self.page_top + self.page_size) then + local max_page_top = cnt - self.page_size + 1 + self.page_top = math.max(1, + math.min(max_page_top, self.selected - self.page_size + buffer + 1)) + end + update_list_scrollbar(self) if (force_cb or delta ~= 0) and self.on_select then self.on_select(self:getSelected()) end end +function List:on_scrollbar(scroll_spec) + local v = 0 + if scroll_spec == 'down_large' then + v = math.floor(self.page_size / 2) + elseif scroll_spec == 'up_large' then + v = -math.floor(self.page_size / 2) + elseif scroll_spec == 'down_small' then + v = 1 + elseif scroll_spec == 'up_small' then + v = -1 + end + + local max_page_top = math.max(1, #self.choices - self.page_size + 1) + self.page_top = math.max(1, math.min(max_page_top, self.page_top + v)) + update_list_scrollbar(self) +end + function List:onRenderBody(dc) local choices = self.choices local top = self.page_top @@ -1122,6 +1241,9 @@ function List:submit2() end function List:onInput(keys) + if self:inputToSubviews(keys) then + return true + end if self.on_submit and keys.SELECT then self:submit() return true From 5722d6914b2ee080890ce44b095806d11b7223d8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 7 Oct 2022 12:45:43 -0700 Subject: [PATCH 741/854] transition Label to use the new generic Scrollbar --- docs/Lua API.rst | 8 +- library/lua/gui/widgets.lua | 171 ++++++++--------------------- test/library/gui/widgets.Label.lua | 64 ----------- 3 files changed, 49 insertions(+), 194 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index b9c09c673..ddb00c61a 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4084,11 +4084,6 @@ It has the following attributes: keys to the number of lines to scroll as positive or negative integers or one of the keywords supported by the ``scroll`` method. The default is up/down arrows scrolling by one line and page up/down scrolling by one page. -:show_scrollbar: Controls scrollbar display: ``false`` for no scrollbar, ``'right'`` or ``'left'`` for - icons next to the text in an additional column (``frame_inset`` is adjusted to have ``.r`` or ``.l`` greater than ``0``), - ``nil`` same as ``'right'`` but changes ``frame_inset`` only if a scroll icon is actually necessary - (if ``getTextHeight()`` is greater than ``frame_body.height``). Default is ``nil``. -:scrollbar: The table of attributes to pass to the `Scrollbar class`_. The text itself is represented as a complex structure, and passed to the object via the ``text`` argument of the constructor, or via @@ -4181,7 +4176,8 @@ The Label widget implements the following methods: This method takes the number of lines to scroll as positive or negative integers or one of the following keywords: ``+page``, ``-page``, - ``+halfpage``, or ``-halfpage``. + ``+halfpage``, or ``-halfpage``. It returns the number of lines that were + actually scrolled (negative for scrolling up). WrappedLabel class ------------------ diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 20aff7aa4..3ac9d3c8b 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -636,12 +636,15 @@ Label.ATTRS{ on_click = DEFAULT_NIL, on_rclick = DEFAULT_NIL, scroll_keys = STANDARDSCROLL, - show_scrollbar = DEFAULT_NIL, -- DEFAULT_NIL, 'right', 'left', false - scrollbar_fg = COLOR_LIGHTGREEN, - scrollbar_bg = COLOR_CYAN } function Label:init(args) + self.scrollbar = Scrollbar{ + frame={r=0}, + on_scroll=self:callback('on_scrollbar')} + + self:addviews{self.scrollbar} + -- use existing saved text if no explicit text was specified. this avoids -- overwriting pre-formatted text that subclasses may have already set self:setText(args.text or self.text) @@ -650,6 +653,12 @@ function Label:init(args) end end +local function update_label_scrollbar(label) + local body_height = label.frame_body and label.frame_body.height or 1 + label.scrollbar:update(label.start_line_num, body_height, + label:getTextHeight()) +end + function Label:setText(text) self.start_line_num = 1 self.text = text @@ -659,81 +668,8 @@ function Label:setText(text) self.frame = self.frame or {} self.frame.h = self:getTextHeight() end -end -function Label:update_scroll_inset() - if self.show_scrollbar == nil then - self._show_scrollbar = self:getTextHeight() > self.frame_body.height and 'right' or false - else - self._show_scrollbar = self.show_scrollbar - end - if self._show_scrollbar then - -- here self._show_scrollbar can only be either - -- 'left' or any true value which we interpret as right - local l,t,r,b = gui.parse_inset(self.frame_inset) - if self._show_scrollbar == 'left' and l <= 0 then - l = 1 - elseif r <= 0 then - r = 1 - end - self.frame_inset = {l=l,t=t,r=r,b=b} - end -end - --- the position is the number of tiles of empty space above the top of the --- scrollbar, and the height is the number of tiles the scrollbar should occupy --- to represent the percentage of text that is on the screen. -local function get_scrollbar_pos_and_height(label) - local first_visible_line = label.start_line_num - local text_height = label:getTextHeight() - local last_visible_line = first_visible_line + label.frame_body.height - 1 - local scrollbar_body_height = label.frame_body.height - 2 - local displayed_lines = last_visible_line - first_visible_line - - local height = math.floor(((displayed_lines-1) * scrollbar_body_height) / - text_height) - - local max_pos = scrollbar_body_height - height - local pos = math.ceil(((first_visible_line-1) * max_pos) / - (text_height - label.frame_body.height)) - - return pos, height -end - -function Label:render_scrollbar(dc, x, y1, y2) - -- render up arrow if we're not at the top - dc:seek(x, y1):char( - self.start_line_num == 1 and NO_ARROW_CHAR or UP_ARROW_CHAR, - self.scrollbar_fg, self.scrollbar_bg) - -- render scrollbar body - local pos, height = get_scrollbar_pos_and_height(self) - local starty = y1 + pos + 1 - local endy = y1 + pos + height - for y=y1+1,y2-1 do - if y >= starty and y <= endy then - dc:seek(x, y):char(BAR_CHAR, self.scrollbar_fg) - else - dc:seek(x, y):char(BAR_BG_CHAR, self.scrollbar_bg) - end - end - -- render down arrow if we're not at the bottom - local last_visible_line = self.start_line_num + self.frame_body.height - 1 - dc:seek(x, y2):char( - last_visible_line >= self:getTextHeight() and - NO_ARROW_CHAR or DOWN_ARROW_CHAR, - self.scrollbar_fg, self.scrollbar_bg) -end - -function Label:computeFrame(parent_rect) - local frame_rect,body_rect = Label.super.computeFrame(self, parent_rect) - - self.frame_rect = frame_rect - self.frame_body = parent_rect:viewport(body_rect or frame_rect) - - self:update_scroll_inset() -- frame_body is now set - - -- recalc with updated frame_inset - return Label.super.computeFrame(self, parent_rect) + update_label_scrollbar(self) end function Label:preUpdateLayout() @@ -743,6 +679,10 @@ function Label:preUpdateLayout() end end +function Label:postUpdateLayout() + update_label_scrollbar(self) +end + function Label:itemById(id) if self.text_ids then return self.text_ids[id] @@ -766,44 +706,19 @@ function Label:onRenderBody(dc) render_text(self,dc,0,0,text_pen,self.text_dpen,is_disabled(self)) end -function Label:onRenderFrame(dc, rect) - if self._show_scrollbar then - local x = self._show_scrollbar == 'left' - and self.frame_body.x1-dc.x1-1 - or self.frame_body.x2-dc.x1+1 - self:render_scrollbar(dc, - x, - self.frame_body.y1-dc.y1, - self.frame_body.y2-dc.y1 - ) - end -end - -function Label:click_scrollbar() - if not self._show_scrollbar then return end - local rect = self.frame_body - local x, y = dscreen.getMousePos() - - if self._show_scrollbar == 'left' and x ~= rect.x1-1 or x ~= rect.x2+1 then - return - end - if y < rect.y1 or y > rect.y2 then - return +function Label:on_scrollbar(scroll_spec) + local v = 0 + if scroll_spec == 'down_large' then + v = '+halfpage' + elseif scroll_spec == 'up_large' then + v = '-halfpage' + elseif scroll_spec == 'down_small' then + v = 1 + elseif scroll_spec == 'up_small' then + v = -1 end - if y == rect.y1 then - return -1 - elseif y == rect.y2 then - return 1 - else - local pos, height = get_scrollbar_pos_and_height(self) - if y <= rect.y1 + pos then - return '-halfpage' - elseif y > rect.y1 + pos + height then - return '+halfpage' - end - end - return nil + self:scroll(v) end function Label:scroll(nlines) @@ -824,24 +739,28 @@ function Label:scroll(nlines) local n = self.start_line_num + nlines n = math.min(n, self:getTextHeight() - self.frame_body.height + 1) n = math.max(n, 1) + nlines = n - self.start_line_num self.start_line_num = n + update_label_scrollbar(self) return nlines end function Label:onInput(keys) if is_disabled(self) then return false end - if keys._MOUSE_L_DOWN then - if not self:scroll(self:click_scrollbar()) and - self:getMousePos() and self.on_click then - self:on_click() - end + if self:inputToSubviews(keys) then + return true + end + if keys._MOUSE_L_DOWN and self:getMousePos() and self.on_click then + self:on_click() + return true end if keys._MOUSE_R_DOWN and self:getMousePos() and self.on_rclick then self:on_rclick() + return true end for k,v in pairs(self.scroll_keys) do - if keys[k] then - self:scroll(v) + if keys[k] and 0 ~= self:scroll(v) then + return true end end return check_text_keys(self, keys) @@ -871,7 +790,7 @@ end -- we can't set the text in init() since we may not yet have a frame that we -- can get wrapping bounds from. function WrappedLabel:postComputeFrame() - local wrapped_text = self:getWrappedText(self.frame_body.width) + local wrapped_text = self:getWrappedText(self.frame_body.width-1) if not wrapped_text then return end local text = {} for _,line in ipairs(wrapped_text:split(NEWLINE)) do @@ -1107,7 +1026,11 @@ function List:postComputeFrame(body) end local function update_list_scrollbar(list) - self.scrollbar:update(list.page_top, list.page_size, #list.choices) + list.scrollbar:update(list.page_top, list.page_size, #list.choices) +end + +function List:postUpdateLayout() + update_list_scrollbar(self) end function List:moveCursor(delta, force_cb) @@ -1159,9 +1082,9 @@ end function List:on_scrollbar(scroll_spec) local v = 0 if scroll_spec == 'down_large' then - v = math.floor(self.page_size / 2) + v = math.ceil(self.page_size / 2) elseif scroll_spec == 'up_large' then - v = -math.floor(self.page_size / 2) + v = -math.ceil(self.page_size / 2) elseif scroll_spec == 'down_small' then v = 1 elseif scroll_spec == 'up_small' then diff --git a/test/library/gui/widgets.Label.lua b/test/library/gui/widgets.Label.lua index c43b5e886..4693d3d0d 100644 --- a/test/library/gui/widgets.Label.lua +++ b/test/library/gui/widgets.Label.lua @@ -11,70 +11,6 @@ fs.ATTRS = { focus_path = 'test-framed-screen', } -function test.correct_frame_body_with_scroll_icons() - local t = {} - for i = 1, 12 do - t[#t+1] = tostring(i) - t[#t+1] = NEWLINE - end - - function fs:init() - self:addviews{ - widgets.Label{ - view_id = 'text', - frame_inset = 0, - text = t, - }, - } - end - - local o = fs{} - expect.eq(o.subviews.text.frame_body.width, 9, "Label's frame_body.x2 and .width should be one smaller because of show_scrollbar.") -end - -function test.correct_frame_body_with_few_text_lines() - local t = {} - for i = 1, 10 do - t[#t+1] = tostring(i) - t[#t+1] = NEWLINE - end - - function fs:init() - self:addviews{ - widgets.Label{ - view_id = 'text', - frame_inset = 0, - text = t, - }, - } - end - - local o = fs{} - expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scrollbar = false.") -end - -function test.correct_frame_body_without_show_scrollbar() - local t = {} - for i = 1, 12 do - t[#t+1] = tostring(i) - t[#t+1] = NEWLINE - end - - function fs:init() - self:addviews{ - widgets.Label{ - view_id = 'text', - frame_inset = 0, - text = t, - show_scrollbar = false, - }, - } - end - - local o = fs{} - expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scrollbar = false.") -end - function test.scroll() local t = {} for i = 1, 12 do From 2bff70a2905c211133e2150885f43aed47ffbc78 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 7 Oct 2022 13:14:52 -0700 Subject: [PATCH 742/854] add unit tests for widgets.Scrollbar --- library/lua/gui/widgets.lua | 5 +- test/library/gui/widgets.Scrollbar.lua | 93 ++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 test/library/gui/widgets.Scrollbar.lua diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 3ac9d3c8b..2eaf80577 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -397,8 +397,9 @@ function Scrollbar:update(top_elem, elems_per_page, num_elems) num_elems)) local max_pos = scrollbar_body_height - height - local pos = math.ceil(((top_elem-1) * max_pos) / - (num_elems - elems_per_page)) + local pos = (num_elems == elems_per_page) and 0 or + math.ceil(((top_elem-1) * max_pos) / + (num_elems - elems_per_page)) self.top_elem = top_elem self.elems_per_page, self.num_elems = elems_per_page, num_elems diff --git a/test/library/gui/widgets.Scrollbar.lua b/test/library/gui/widgets.Scrollbar.lua new file mode 100644 index 000000000..dbe033ba4 --- /dev/null +++ b/test/library/gui/widgets.Scrollbar.lua @@ -0,0 +1,93 @@ +local gui = require('gui') +local widgets = require('gui.widgets') + +function test.update() + local s = widgets.Scrollbar{} + s.frame_body = {height=100} -- give us some space to work with + + -- initial defaults + expect.eq(1, s.top_elem) + expect.eq(1, s.elems_per_page) + expect.eq(1, s.num_elems) + expect.eq(0, s.bar_offset) + expect.eq(1, s.bar_height) + + -- top_elem, elems_per_page, num_elems + s:update(1, 10, 0) + expect.eq(1, s.top_elem) + expect.eq(10, s.elems_per_page) + expect.eq(0, s.num_elems) + expect.eq(0, s.bar_offset) + expect.eq(1, s.bar_height) + + -- first 10 of 50 shown + s:update(1, 10, 50) + expect.eq(1, s.top_elem) + expect.eq(10, s.elems_per_page) + expect.eq(50, s.num_elems) + expect.eq(0, s.bar_offset) + expect.eq(19, s.bar_height) + + -- bottom 10 of 50 shown + s:update(41, 10, 50) + expect.eq(41, s.top_elem) + expect.eq(10, s.elems_per_page) + expect.eq(50, s.num_elems) + expect.eq(79, s.bar_offset) + expect.eq(19, s.bar_height) + + -- ~middle 10 of 50 shown + s:update(23, 10, 50) + expect.eq(23, s.top_elem) + expect.eq(10, s.elems_per_page) + expect.eq(50, s.num_elems) + expect.eq(44, s.bar_offset) + expect.eq(19, s.bar_height) +end + +function test.onInput() + local spec = nil + local mock_on_scroll = function(scroll_spec) spec = scroll_spec end + local s = widgets.Scrollbar{on_scroll=mock_on_scroll} + s.frame_body = {height=100} -- give us some space to work with + local y = nil + s.getMousePos = function() return 0, y end + + -- put scrollbar somewhere in the middle so we can click above and below it + s:update(23, 10, 50) + + expect.false_(s:onInput{}, 'no mouse down') + expect.false_(s:onInput{_MOUSE_L_DOWN=true}, 'no y coord') + + spec, y = nil, 0 + expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.eq('up_small', spec, 'on up arrow') + + spec, y = nil, 1 + expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.eq('up_large', spec, 'on body above bar') + + spec, y = nil, 44 + expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.eq('up_large', spec, 'on body just above bar') + + spec, y = nil, 45 + expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.nil_(spec, 'on top of bar') + + spec, y = nil, 63 + expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.nil_(spec, 'on bottom of bar') + + spec, y = nil, 64 + expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.eq('down_large', spec, 'on body just below bar') + + spec, y = nil, 98 + expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.eq('down_large', spec, 'on body below bar') + + spec, y = nil, 99 + expect.true_(s:onInput{_MOUSE_L_DOWN=true}) + expect.eq('down_small', spec, 'on down arrow') +end From 35eb4e08dd53133abc14fdb189bfc9dd8fa848b7 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 7 Oct 2022 15:40:05 -0700 Subject: [PATCH 743/854] hold down the mouse button to continue scrolling --- docs/Lua API.rst | 12 +++++++++++ docs/changelog.txt | 1 + library/lua/gui/widgets.lua | 41 ++++++++++++++++++++++++++++++------- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index ddb00c61a..8960cb15c 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4064,6 +4064,18 @@ direction. The amount of scrolling done in each case in determined by the associated widget, and after scrolling is complete, the associated widget must call ``scrollbar:update()`` with updated new display info. +You can hold down the mouse button to scroll multiple times, just like in a +normal browser scrollbar. The speed of scroll events when the mouse button is +held down is controlled by two global variables: + +:``SCROLL_INITIAL_DELAY_MS``: The delay before the second scroll event. +:``SCROLL_DELAY_MS``: The delay between further scroll events. + +The defaults are 300 and 20, respectively, but they can be overridden by the +user in their :file:`dfhack-config/init/dfhack.init` file, for example:: + + :lua require('gui.widgets').SCROLL_DELAY_MS = 100 + Label class ----------- diff --git a/docs/changelog.txt b/docs/changelog.txt index b1d5300a9..1669ea9dd 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output. - `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down - UX: List widgets now have mouse-interactive scrollbars +- UX: You can now hold down the mouse button on a scrollbar to make it scroll multiple times. ## Documentation diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 2eaf80577..e5b7e9bfd 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -363,6 +363,11 @@ end -- Scrollbar -- --------------- +-- these can be overridden by the user, e.g.: +-- require('gui.widgets').SCROLL_DELAY_MS = 100 +SCROLL_INITIAL_DELAY_MS = 300 +SCROLL_DELAY_MS = 20 + Scrollbar = defclass(Scrollbar, Widget) Scrollbar.ATTRS{ @@ -377,6 +382,9 @@ function Scrollbar:preinit(init_table) end function Scrollbar:init() + self.last_scroll_ms = 0 + self.is_first_click = false + self.scroll_spec = nil self:update(1, 1, 1) end @@ -434,19 +442,38 @@ function Scrollbar:onRenderBody(dc) dc:seek(0, dc.height-1):char( last_visible_el >= self.num_elems and NO_ARROW_CHAR or DOWN_ARROW_CHAR, self.fg, self.bg) + -- manage state for continuous scrolling + if self.last_scroll_ms == 0 or not self.on_scroll then return end + if df.global.enabler.mouse_lbut_down == 0 then + self.last_scroll_ms = 0 + self.scroll_spec = nil + return + end + local now = dfhack.getTickCount() + local delay = self.is_first_click and + SCROLL_INITIAL_DELAY_MS or SCROLL_DELAY_MS + if now - self.last_scroll_ms >= delay then + self.is_first_click = false + self.on_scroll(self.scroll_spec) + self.last_scroll_ms = now + end end function Scrollbar:onInput(keys) if not keys._MOUSE_L_DOWN or not self.on_scroll then return false end local _,y = self:getMousePos() if not y then return false end - local scroll = nil - if y == 0 then scroll = 'up_small' - elseif y == self.frame_body.height - 1 then scroll = 'down_small' - elseif y <= self.bar_offset then scroll = 'up_large' - elseif y > self.bar_offset + self.bar_height then scroll = 'down_large' - end - if scroll then self.on_scroll(scroll) end + local scroll_spec = nil + if y == 0 then scroll_spec = 'up_small' + elseif y == self.frame_body.height - 1 then scroll_spec = 'down_small' + elseif y <= self.bar_offset then scroll_spec = 'up_large' + elseif y > self.bar_offset + self.bar_height then scroll_spec = 'down_large' + end + self.scroll_spec = scroll_spec + if scroll_spec then self.on_scroll(scroll_spec) end + -- reset continuous scroll state + self.is_first_click = true + self.last_scroll_ms = dfhack.getTickCount() return true end From ba36e72b3328e3cb00aebe6590d96f66234896ec Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 7 Oct 2022 16:27:19 -0700 Subject: [PATCH 744/854] support click and drag for scrollbars --- docs/changelog.txt | 1 + library/lua/gui/widgets.lua | 31 ++++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 1669ea9dd..f9b7accc5 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -43,6 +43,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down - UX: List widgets now have mouse-interactive scrollbars - UX: You can now hold down the mouse button on a scrollbar to make it scroll multiple times. +- UX: You can now drag the scrollbar to scroll to a specific spot ## Documentation diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index e5b7e9bfd..f63b708cf 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -385,6 +385,7 @@ function Scrollbar:init() self.last_scroll_ms = 0 self.is_first_click = false self.scroll_spec = nil + self.is_dragging = false -- index of the scrollbar tile that we're dragging self:update(1, 1, 1) end @@ -414,6 +415,22 @@ function Scrollbar:update(top_elem, elems_per_page, num_elems) self.bar_offset, self.bar_height = pos, height end +local function scrollbar_do_drag(scrollbar) + local x,y = dfhack.screen.getMousePos() + x,y = scrollbar.frame_body:localXY(x,y) + local bar_idx = y - scrollbar.bar_offset + local delta = bar_idx - scrollbar.is_dragging + if delta < -2 then + scrollbar.on_scroll('up_large') + elseif delta < 0 then + scrollbar.on_scroll('up_small') + elseif delta > 2 then + scrollbar.on_scroll('down_large') + elseif delta > 0 then + scrollbar.on_scroll('down_small') + end +end + local UP_ARROW_CHAR = string.char(24) local DOWN_ARROW_CHAR = string.char(25) local NO_ARROW_CHAR = string.char(32) @@ -442,13 +459,18 @@ function Scrollbar:onRenderBody(dc) dc:seek(0, dc.height-1):char( last_visible_el >= self.num_elems and NO_ARROW_CHAR or DOWN_ARROW_CHAR, self.fg, self.bg) - -- manage state for continuous scrolling - if self.last_scroll_ms == 0 or not self.on_scroll then return end + if not self.on_scroll then return end + -- manage state for dragging and continuous scrolling + if self.is_dragging then + scrollbar_do_drag(self) + end if df.global.enabler.mouse_lbut_down == 0 then self.last_scroll_ms = 0 + self.is_dragging = false self.scroll_spec = nil return end + if self.last_scroll_ms == 0 then return end local now = dfhack.getTickCount() local delay = self.is_first_click and SCROLL_INITIAL_DELAY_MS or SCROLL_DELAY_MS @@ -468,9 +490,12 @@ function Scrollbar:onInput(keys) elseif y == self.frame_body.height - 1 then scroll_spec = 'down_small' elseif y <= self.bar_offset then scroll_spec = 'up_large' elseif y > self.bar_offset + self.bar_height then scroll_spec = 'down_large' + else + self.is_dragging = y - self.bar_offset + return true end self.scroll_spec = scroll_spec - if scroll_spec then self.on_scroll(scroll_spec) end + self.on_scroll(scroll_spec) -- reset continuous scroll state self.is_first_click = true self.last_scroll_ms = dfhack.getTickCount() From 31efd4177f05f328c108c127acf74d0977227231 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 7 Oct 2022 23:33:28 +0000 Subject: [PATCH 745/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 41bfa9005..f38299b76 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 41bfa9005ec78094388518d40e7aaa43f7d35890 +Subproject commit f38299b764f3947a2ebf2bc112b82467bd47b19b From 9a0f9f210b2e2604c8fe7739350af1bbafd1d7c4 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 7 Oct 2022 16:38:44 -0700 Subject: [PATCH 746/854] update docs for clicking and dragging scrollbars --- docs/Lua API.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 8960cb15c..c2b84f0f0 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4064,9 +4064,11 @@ direction. The amount of scrolling done in each case in determined by the associated widget, and after scrolling is complete, the associated widget must call ``scrollbar:update()`` with updated new display info. -You can hold down the mouse button to scroll multiple times, just like in a -normal browser scrollbar. The speed of scroll events when the mouse button is -held down is controlled by two global variables: +You can click and drag the scrollbar to scroll to a specific spot, or you can +click and hold on the end arrows or in the unfilled portion of the scrollbar to +scroll multiple times, just like in a normal browser scrollbar. The speed of +scroll events when the mouse button is held down is controlled by two global +variables: :``SCROLL_INITIAL_DELAY_MS``: The delay before the second scroll event. :``SCROLL_DELAY_MS``: The delay between further scroll events. From 0dad512b5a1fcd40d93539260602fb3caf309955 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 8 Oct 2022 07:22:44 +0000 Subject: [PATCH 747/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index f38299b76..3c27569e6 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit f38299b764f3947a2ebf2bc112b82467bd47b19b +Subproject commit 3c27569e6834151abe4586dc0437d4efad3c7dcd From d650ba23777726899b56e2a4ebc29c790a56de5b Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 10 Oct 2022 16:42:32 -0700 Subject: [PATCH 748/854] ensure scrollbar can never get ahead of the cursor --- library/lua/gui/widgets.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index f63b708cf..eb8d92244 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -420,11 +420,11 @@ local function scrollbar_do_drag(scrollbar) x,y = scrollbar.frame_body:localXY(x,y) local bar_idx = y - scrollbar.bar_offset local delta = bar_idx - scrollbar.is_dragging - if delta < -2 then + if delta < -scrollbar.bar_height then scrollbar.on_scroll('up_large') elseif delta < 0 then scrollbar.on_scroll('up_small') - elseif delta > 2 then + elseif delta > scrollbar.bar_height then scrollbar.on_scroll('down_large') elseif delta > 0 then scrollbar.on_scroll('down_small') From cc61d4a82aad31277c7ba663644357d8e88bdc69 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 10 Oct 2022 17:03:57 -0700 Subject: [PATCH 749/854] use rendered help instead of hard-coded --- plugins/blueprint.cpp | 37 ++++++++++++------------------------- plugins/lua/blueprint.lua | 31 ------------------------------- 2 files changed, 12 insertions(+), 56 deletions(-) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 3a0cb60b4..7919c4c5c 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -1187,23 +1187,11 @@ static bool get_options(color_ostream &out, return true; } -static void print_help(color_ostream &out) { - auto L = Lua::Core::State; - Lua::StackUnwinder top(L); - - if (!lua_checkstack(L, 1) || - !Lua::PushModulePublic(out, L, "plugins.blueprint", "print_help") || - !Lua::SafeCall(out, L, 0, 0)) - { - out.printerr("Failed to load blueprint Lua code\n"); - } -} - // returns whether blueprint generation was successful. populates files with the // names of the files that were generated -static bool do_blueprint(color_ostream &out, - const vector ¶meters, - vector &files) { +static command_result do_blueprint(color_ostream &out, + const vector ¶meters, + vector &files) { CoreSuspender suspend; if (parameters.size() >= 1 && parameters[0] == "gui") { @@ -1221,13 +1209,12 @@ static bool do_blueprint(color_ostream &out, blueprint_options options; if (!get_options(out, options, parameters) || options.help) { - print_help(out); - return options.help; + return CR_WRONG_USAGE; } if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); - return false; + return CR_FAILURE; } // start coordinates can come from either the commandline or the map cursor @@ -1236,13 +1223,13 @@ static bool do_blueprint(color_ostream &out, if (!Gui::getCursorCoords(start)) { out.printerr("Can't get cursor coords! Make sure you specify the" " --cursor parameter or have an active cursor in DF.\n"); - return false; + return CR_FAILURE; } } if (!Maps::isValidTilePos(start)) { out.printerr("Invalid start position: %d,%d,%d\n", start.x, start.y, start.z); - return false; + return CR_FAILURE; } // end coords are one beyond the last processed coordinate. note that @@ -1265,7 +1252,7 @@ static bool do_blueprint(color_ostream &out, bool ok = do_transform(out, start, end, options, files); cache(NULL); - return ok; + return ok ? CR_OK : CR_FAILURE; } // entrypoint when called from Lua. returns the names of the generated files @@ -1284,7 +1271,7 @@ static int run(lua_State *L) { color_ostream *out = Lua::GetOutput(L); if (!out) out = &Core::getInstance().getConsole(); - if (do_blueprint(*out, argv, files)) { + if (CR_OK == do_blueprint(*out, argv, files)) { Lua::PushVector(L, files); return 1; } @@ -1294,13 +1281,13 @@ static int run(lua_State *L) { command_result blueprint(color_ostream &out, vector ¶meters) { vector files; - if (do_blueprint(out, parameters, files)) { + command_result cr = do_blueprint(out, parameters, files); + if (cr == CR_OK) { out.print("Generated blueprint file(s):\n"); for (string &fname : files) out.print(" %s\n", fname.c_str()); - return CR_OK; } - return CR_FAILURE; + return cr; } DFHACK_PLUGIN_LUA_COMMANDS { diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua index 7b0076e14..4cc866650 100644 --- a/plugins/lua/blueprint.lua +++ b/plugins/lua/blueprint.lua @@ -3,37 +3,6 @@ local _ENV = mkmodule('plugins.blueprint') local argparse = require('argparse') local utils = require('utils') --- the info here is very basic and minimal, so hopefully we won't need to change --- it when features are added and the full blueprint docs in Plugins.rst are --- updated. -local help_text = [=[ - -blueprint -========= - -Records the structure of a portion of your fortress in quickfort blueprints. - -Usage: - - blueprint [] [ []] [] - blueprint gui [ []] [] - -Examples: - -blueprint gui - Runs gui/blueprint, the interactive blueprint frontend, where all - configuration can be set visually and interactively. - -blueprint 30 40 bedrooms - Generates blueprints for an area 30 tiles wide by 40 tiles tall, starting - from the active cursor on the current z-level. Output files are written to - the "blueprints" directory. - -See the online DFHack documentation for more examples and details. -]=] - -function print_help() print(help_text) end - local valid_phase_list = { 'dig', 'carve', From 8f024216b833301706685dc0954f0ad90ca7fc63 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 11 Oct 2022 07:40:53 +0000 Subject: [PATCH 750/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 3c27569e6..937cf27f9 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 3c27569e6834151abe4586dc0437d4efad3c7dcd +Subproject commit 937cf27f9b41301be6df0fe1d75d77b6f089f688 From 598f2c4b120d53b79dbd376b806120ede4bdf15d Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 12 Oct 2022 10:10:53 -0700 Subject: [PATCH 751/854] support --smooth option for blueprints --- docs/plugins/blueprint.rst | 4 ++++ plugins/blueprint.cpp | 37 ++++++++++++++++++++++++++++++++++--- plugins/lua/blueprint.lua | 1 + test/plugins/blueprint.lua | 6 ++++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst index 9d7d7ef8d..b999c0e99 100644 --- a/docs/plugins/blueprint.rst +++ b/docs/plugins/blueprint.rst @@ -103,6 +103,10 @@ Options to surround the parameter string in double quotes: ``"-s10,10,central stairs"`` or ``--playback-start "10,10,central stairs"`` or ``"--playback-start=10,10,central stairs"``. +``--smooth`` + Record all smooth tiles in the ``smooth`` phase. If this parameter is not + specified, only tiles that will later be carved into fortifications or + engraved will be smoothed. ``-t``, ``--splitby `` Split blueprints into multiple files. See the `Splitting output into multiple files`_ section below for details. If not specified, defaults to diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 7919c4c5c..20aa1aa99 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -76,6 +76,8 @@ struct blueprint_options { // base name to use for generated files string name; + // whether to capture all smoothed tiles + bool smooth = false; // whether to capture engravings and smooth the tiles that will be engraved bool engrave = false; @@ -103,6 +105,7 @@ static const struct_field_info blueprint_options_fields[] = { { struct_field_info::PRIMITIVE, "height", offsetof(blueprint_options, height), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "depth", offsetof(blueprint_options, depth), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "name", offsetof(blueprint_options, name), df::identity_traits::get(), 0, 0 }, + { struct_field_info::PRIMITIVE, "smooth", offsetof(blueprint_options, smooth), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "engrave", offsetof(blueprint_options, engrave), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "auto_phase", offsetof(blueprint_options, auto_phase), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "dig", offsetof(blueprint_options, dig), &df::identity_traits::identity, 0, 0 }, @@ -219,8 +222,8 @@ static const char * get_tile_smooth_minimal(const df::coord &pos, return NULL; } -static const char * get_tile_smooth(const df::coord &pos, - const tile_context &tc) { +static const char * get_tile_smooth_with_engravings(const df::coord &pos, + const tile_context &tc) { const char * smooth_minimal = get_tile_smooth_minimal(pos, tc); if (smooth_minimal) return smooth_minimal; @@ -244,6 +247,30 @@ static const char * get_tile_smooth(const df::coord &pos, return NULL; } +static const char * get_tile_smooth_all(const df::coord &pos, + const tile_context &tc) { + const char * smooth_minimal = get_tile_smooth_minimal(pos, tc); + if (smooth_minimal) + return smooth_minimal; + + df::tiletype *tt = Maps::getTileType(pos); + if (!tt) + return NULL; + + switch (tileShape(*tt)) + { + case tiletype_shape::FLOOR: + case tiletype_shape::WALL: + if (tileSpecial(*tt) == tiletype_special::SMOOTH) + return "s"; + break; + default: + break; + } + + return NULL; +} + static const char * get_track_str(const char *prefix, df::tiletype tt) { TileDirection tdir = tileDirection(tt); @@ -1096,9 +1123,13 @@ static bool do_transform(color_ostream &out, vector processors; + get_tile_fn* smooth_get_tile_fn = get_tile_smooth_minimal; + if (opts.engrave) smooth_get_tile_fn = get_tile_smooth_with_engravings; + if (opts.smooth) smooth_get_tile_fn = get_tile_smooth_all; + add_processor(processors, opts, "dig", "dig", opts.dig, get_tile_dig); add_processor(processors, opts, "dig", "smooth", opts.carve, - opts.engrave ? get_tile_smooth : get_tile_smooth_minimal); + smooth_get_tile_fn); add_processor(processors, opts, "dig", "carve", opts.carve, opts.engrave ? get_tile_carve : get_tile_carve_minimal); add_processor(processors, opts, "build", "build", opts.build, diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua index 4cc866650..5c73be7c0 100644 --- a/plugins/lua/blueprint.lua +++ b/plugins/lua/blueprint.lua @@ -123,6 +123,7 @@ local function process_args(opts, args) {'h', 'help', handler=function() opts.help = true end}, {'s', 'playback-start', hasArg=true, handler=function(optarg) parse_start(opts, optarg) end}, + {nil, 'smooth', handler=function() opts.smooth = true end}, {'t', 'splitby', hasArg=true, handler=function(optarg) parse_split_strategy(opts, optarg) end}, }) diff --git a/test/plugins/blueprint.lua b/test/plugins/blueprint.lua index 27a2634b4..a19d519b8 100644 --- a/test/plugins/blueprint.lua +++ b/test/plugins/blueprint.lua @@ -35,6 +35,12 @@ function test.parse_gui_commandline() name='blueprint', engrave=true,}, opts) + opts = {} + b.parse_gui_commandline(opts, {'--smooth'}) + expect.table_eq({auto_phase=true, format='minimal', split_strategy='none', + name='blueprint', smooth=true,}, + opts) + opts = {} b.parse_gui_commandline(opts, {'--engrave'}) expect.table_eq({auto_phase=true, format='minimal', split_strategy='none', From a49538695d8a239ea31ae1299e9e15a65b7487fb Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 12 Oct 2022 11:21:59 -0700 Subject: [PATCH 752/854] Ensures consistent formatting and improves some comments --- plugins/spectate.cpp | 52 +++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/plugins/spectate.cpp b/plugins/spectate.cpp index b0bd190d9..2cf23df1d 100644 --- a/plugins/spectate.cpp +++ b/plugins/spectate.cpp @@ -147,7 +147,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { if (unpause_enabled) { // player asked for auto-unpause enabled World::SaveAnnouncementSettings(); - if (World::DisableAnnouncementPausing()){ + if (World::DisableAnnouncementPausing()) { // now that we've got what we want, we can lock it down lock_collision = false; pause_lock->lock(); @@ -163,25 +163,25 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { Gui::getCurViewscreen(true)->feed_key(interface_key::CLOSE_MEGA_ANNOUNCEMENT); } if (disengage_enabled) { - if (our_dorf && our_dorf->id != df::global::ui->follow_unit){ + if (our_dorf && our_dorf->id != df::global::ui->follow_unit) { plugin_enable(out, false); } } return DFHack::CR_OK; } -void enable_auto_unpause(color_ostream &out, bool state){ - if(unpause_enabled != state && lock_collision) { - // when enabled, lock collision means announcements haven't been disabled - // when disabled, lock collision means announcement are still disabled - // the only state left to consider here is what the lock should be set to +void enable_auto_unpause(color_ostream &out, bool state) { + // lock_collision == true means: enable_auto_unpause() was already invoked and didn't complete + // The onupdate function above ensure the procedure properly completes, thus we only care about + // state reversal here ergo `enabled != state` + if (lock_collision && unpause_enabled != state) { + // if unpaused_enabled is true, then a lock collision means: we couldn't save/disable the pause settings, + // therefore nothing to revert and the lock won't even be engaged (nothing to unlock) lock_collision = false; unpause_enabled = state; if (unpause_enabled) { + // a collision means we couldn't restore the pause settings, therefore we only need re-engage the lock pause_lock->lock(); - } else { - // this one should be redundant, the lock should already be unlocked right now - pause_lock->unlock(); } return; } @@ -201,37 +201,39 @@ void enable_auto_unpause(color_ostream &out, bool state){ lock_collision = true; } } - if (lock_collision){ - out.printerr("auto-unpause: must wait for another Pausing::AnnouncementLock to be lifted. This setting will complete when the lock lifts.\n"); + if (lock_collision) { + out.printerr( + "auto-unpause: must wait for another Pausing::AnnouncementLock to be lifted." + " This setting will complete when the lock lifts.\n"); } } command_result spectate (color_ostream &out, std::vector & parameters) { - if(!parameters.empty()) { + if (!parameters.empty()) { if (parameters.size() % 2 != 0) { return DFHack::CR_WRONG_USAGE; } - for (size_t i = 0; i+1 < parameters.size(); i += 2) { + for (size_t i = 0; i + 1 < parameters.size(); i += 2) { if (parameters[i] == "auto-unpause") { - if (parameters[i+1] == "0") { + if (parameters[i + 1] == "0") { enable_auto_unpause(out, false); - } else if (parameters[i+1] == "1") { + } else if (parameters[i + 1] == "1") { enable_auto_unpause(out, true); } else { return DFHack::CR_WRONG_USAGE; } } else if (parameters[i] == "auto-disengage") { - if (parameters[i+1] == "0") { + if (parameters[i + 1] == "0") { disengage_enabled = false; - } else if (parameters[i+1] == "1") { + } else if (parameters[i + 1] == "1") { disengage_enabled = true; } else { return DFHack::CR_WRONG_USAGE; } } else if (parameters[i] == "focus-jobs") { - if (parameters[i+1] == "0") { + if (parameters[i + 1] == "0") { focus_jobs_enabled = false; - } else if (parameters[i+1] == "1") { + } else if (parameters[i + 1] == "1") { focus_jobs_enabled = true; } else { return DFHack::CR_WRONG_USAGE; @@ -261,13 +263,13 @@ command_result spectate (color_ostream &out, std::vector & paramet void onTick(color_ostream& out, void* ptr) { if (!df::global::ui) return; int32_t tick = df::global::world->frame_counter; - if(our_dorf){ - if(!Units::isAlive(our_dorf)){ + if (our_dorf) { + if (!Units::isAlive(our_dorf)) { following_dwarf = false; df::global::ui->follow_unit = -1; } } - if (!following_dwarf || (focus_jobs_enabled && !job_watched) || (tick - timestamp) > (int32_t)tick_threshold) { + if (!following_dwarf || (focus_jobs_enabled && !job_watched) || (tick - timestamp) > (int32_t) tick_threshold) { std::vector dwarves; for (auto unit: df::global::world->units.active) { if (!Units::isCitizen(unit)) { @@ -298,7 +300,7 @@ void onJobStart(color_ostream& out, void* job_ptr) { int zcount = ++freq[job->pos.z]; job_tracker.emplace(job->id); // if we're not doing anything~ then let's pick something - if ((focus_jobs_enabled && !job_watched) || (tick - timestamp) > (int32_t)tick_threshold) { + if ((focus_jobs_enabled && !job_watched) || (tick - timestamp) > (int32_t) tick_threshold) { following_dwarf = true; // todo: allow the user to configure b, and also revise the math const double b = base; @@ -330,7 +332,7 @@ void onJobStart(color_ostream& out, void* job_ptr) { // every job completed can be forgotten about void onJobCompletion(color_ostream &out, void* job_ptr) { - auto job = (df::job*)job_ptr; + auto job = (df::job*) job_ptr; // forget about it freq[job->pos.z]--; freq[job->pos.z] = freq[job->pos.z] < 0 ? 0 : freq[job->pos.z]; From 1cf96883490050a2ad89a61b0518764f57df427c Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Wed, 12 Oct 2022 21:10:22 +0100 Subject: [PATCH 753/854] Implement/change/expose constructions findAtTile & insert (not building) --- docs/Lua API.rst | 7 +++++++ library/LuaApi.cpp | 9 +++++++++ library/modules/Constructions.cpp | 15 +++++++++++---- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index c2b84f0f0..75196e7b9 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1906,6 +1906,13 @@ Constructions module coordinates, designates it for removal, or instantly cancels the planned one. Returns *true, was_only_planned* if removed; or *false* if none found. +* ``dfhack.constructions.findAtTile(pos)``, or ``findAtTile(x,y,z)`` + + Returns the construction at the given position, or ``nil`` if there isn't one. + +* ``dfhack.constructions.insert(construction)`` + + Properly inserts the given construction into the game. Kitchen module -------------- diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 3a83f7aef..463cf3fcc 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2276,8 +2276,17 @@ static int constructions_designateRemove(lua_State *L) return 2; } +static int constructions_findAtTile(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + Lua::PushDFObject(L, Constructions::findAtTile(pos)); + return 1; +} + static const luaL_Reg dfhack_constructions_funcs[] = { { "designateRemove", constructions_designateRemove }, + { "findAtTile", constructions_findAtTile }, + WRAPM(Constructions, insert), { NULL, NULL } }; diff --git a/library/modules/Constructions.cpp b/library/modules/Constructions.cpp index 5fc632500..407e95f76 100644 --- a/library/modules/Constructions.cpp +++ b/library/modules/Constructions.cpp @@ -54,11 +54,18 @@ using df::global::world; df::construction * Constructions::findAtTile(df::coord pos) { - for (auto it = world->constructions.begin(); it != world->constructions.end(); ++it) { - if ((*it)->pos == pos) - return *it; + int index = binsearch_index(world->constructions, pos); + if (index == -1) { + return NULL; } - return NULL; + return world->constructions[index]; +} + +bool Constructions::insert(df::construction * constr) +{ + bool toInsert; + insert_into_vector(world->constructions, &df::construction::pos, constr, &toInsert); + return toInsert; } bool Constructions::designateNew(df::coord pos, df::construction_type type, From b78af724031723b7436ae122e0fb22e498769a5c Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 12 Oct 2022 13:31:46 -0700 Subject: [PATCH 754/854] record built constructions in blueprint --- docs/plugins/blueprint.rst | 18 ++-- library/include/TileTypes.h | 2 +- plugins/blueprint.cpp | 163 ++++++++++++++++++++++++------------ plugins/lua/blueprint.lua | 1 + 4 files changed, 122 insertions(+), 62 deletions(-) diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst index b999c0e99..bde5bb77e 100644 --- a/docs/plugins/blueprint.rst +++ b/docs/plugins/blueprint.rst @@ -7,11 +7,11 @@ blueprint With ``blueprint``, you can export the structure of a portion of your fortress in a blueprint file that you (or anyone else) can later play back with -`quickfort`. +`gui/quickfort`. Blueprints are ``.csv`` or ``.xlsx`` files created in the ``blueprints`` subdirectory of your DF folder. The map area to turn into a blueprint is either -selected interactively with the ``blueprint gui`` command or, if the GUI is not +selected interactively with the ``gui/blueprint`` command or, if the GUI is not used, starts at the active cursor location and extends right and down for the requested width and height. @@ -27,16 +27,16 @@ Examples -------- ``blueprint gui`` - Runs `gui/blueprint`, the interactive frontend, where all configuration for - a ``blueprint`` command can be set visually and interactively. + Runs `gui/blueprint`, the GUI frontend, where all configuration for a + ``blueprint`` command can be set visually and interactively. ``blueprint 30 40 bedrooms`` Generates blueprints for an area 30 tiles wide by 40 tiles tall, starting from the active cursor on the current z-level. Blueprints are written to ``bedrooms.csv`` in the ``blueprints`` directory. ``blueprint 30 40 bedrooms dig --cursor 108,100,150`` Generates only the ``#dig`` blueprint in the ``bedrooms.csv`` file, and - the start of the blueprint area is set to a specific value instead of using - the in-game cursor position. + the start of the blueprint area is set to a specific coordinate instead of + using the in-game cursor position. Positional parameters --------------------- @@ -66,8 +66,12 @@ phases; just separate them with a space. Generate quickfort ``#dig`` blueprints for digging natural stone. ``carve`` Generate quickfort ``#dig`` blueprints for smoothing and carving. +``construct`` + Generate quickfort ``#build`` blueprints for constructions (e.g. flooring + and walls). ``build`` - Generate quickfort ``#build`` blueprints for constructions and buildings. + Generate quickfort ``#build`` blueprints for buildings (including + furniture). ``place`` Generate quickfort ``#place`` blueprints for placing stockpiles. ``zone`` diff --git a/library/include/TileTypes.h b/library/include/TileTypes.h index 5dbc81d0d..8e5c82cb2 100644 --- a/library/include/TileTypes.h +++ b/library/include/TileTypes.h @@ -116,7 +116,7 @@ namespace DFHack //This is a static string, overwritten with every call! //Support values > 2 even though they should never happen. //Copy string if it will be used. - inline char * getStr() const + inline const char * getStr() const { static char str[16]; diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 20aa1aa99..3dd912d28 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -85,11 +85,12 @@ struct blueprint_options { bool auto_phase = false; // if not autodetecting, which phases to output - bool dig = false; + bool dig = false; bool carve = false; + bool construct = false; bool build = false; bool place = false; - bool zone = false; + bool zone = false; bool query = false; static struct_identity _identity; @@ -110,6 +111,7 @@ static const struct_field_info blueprint_options_fields[] = { { struct_field_info::PRIMITIVE, "auto_phase", offsetof(blueprint_options, auto_phase), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "dig", offsetof(blueprint_options, dig), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "carve", offsetof(blueprint_options, carve), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "construct", offsetof(blueprint_options, construct), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "build", offsetof(blueprint_options, build), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "place", offsetof(blueprint_options, place), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "zone", offsetof(blueprint_options, zone), &df::identity_traits::identity, 0, 0 }, @@ -332,6 +334,108 @@ static const char * get_tile_carve(const df::coord &pos, const tile_context &tc) return NULL; } +static const char * get_construction_str(df::building *b) { + df::building_constructionst *cons = + virtual_cast(b); + if (!cons) + return "~"; + + switch (cons->type) { + case construction_type::Fortification: return "CF"; + case construction_type::Wall: return "Cw"; + case construction_type::Floor: return "Cf"; + case construction_type::UpStair: return "Cu"; + case construction_type::DownStair: return "Cd"; + case construction_type::UpDownStair: return "Cx"; + case construction_type::Ramp: return "Cr"; + case construction_type::TrackN: return "trackN"; + case construction_type::TrackS: return "trackS"; + case construction_type::TrackE: return "trackE"; + case construction_type::TrackW: return "trackW"; + case construction_type::TrackNS: return "trackNS"; + case construction_type::TrackNE: return "trackNE"; + case construction_type::TrackNW: return "trackNW"; + case construction_type::TrackSE: return "trackSE"; + case construction_type::TrackSW: return "trackSW"; + case construction_type::TrackEW: return "trackEW"; + case construction_type::TrackNSE: return "trackNSE"; + case construction_type::TrackNSW: return "trackNSW"; + case construction_type::TrackNEW: return "trackNEW"; + case construction_type::TrackSEW: return "trackSEW"; + case construction_type::TrackNSEW: return "trackNSEW"; + case construction_type::TrackRampN: return "trackrampN"; + case construction_type::TrackRampS: return "trackrampS"; + case construction_type::TrackRampE: return "trackrampE"; + case construction_type::TrackRampW: return "trackrampW"; + case construction_type::TrackRampNS: return "trackrampNS"; + case construction_type::TrackRampNE: return "trackrampNE"; + case construction_type::TrackRampNW: return "trackrampNW"; + case construction_type::TrackRampSE: return "trackrampSE"; + case construction_type::TrackRampSW: return "trackrampSW"; + case construction_type::TrackRampEW: return "trackrampEW"; + case construction_type::TrackRampNSE: return "trackrampNSE"; + case construction_type::TrackRampNSW: return "trackrampNSW"; + case construction_type::TrackRampNEW: return "trackrampNEW"; + case construction_type::TrackRampSEW: return "trackrampSEW"; + case construction_type::TrackRampNSEW: return "trackrampNSEW"; + case construction_type::NONE: + default: + return "~"; + } +} + +static const char * get_constructed_track_str(df::tiletype *tt, + const char * base) { + TileDirection dir = tileDirection(*tt); + if (!dir.whole) + return "~"; + + std::ostringstream str; + str << base; + if (dir.north) str << "N"; + if (dir.south) str << "S"; + if (dir.east) str << "E"; + if (dir.west) str << "W"; + + return cache(str); +} + +static const char * get_constructed_floor_str(df::tiletype *tt) { + if (tileSpecial(*tt) != df::tiletype_special::TRACK) + return "Cf"; + return get_constructed_track_str(tt, "track"); +} + +static const char * get_constructed_ramp_str(df::tiletype *tt) { + if (tileSpecial(*tt) != df::tiletype_special::TRACK) + return "Cr"; + return get_constructed_track_str(tt, "trackramp"); +} + +static const char * get_tile_construct(const df::coord &pos, + const tile_context &ctx) { + if (ctx.b && ctx.b->getType() == building_type::Construction) + return get_construction_str(ctx.b); + + df::tiletype *tt = Maps::getTileType(pos); + if (!tt || tileMaterial(*tt) != df::tiletype_material::CONSTRUCTION) + return NULL; + + switch (tileShape(*tt)) { + case tiletype_shape::WALL: return "Cw"; + case tiletype_shape::FLOOR: return get_constructed_floor_str(tt); + case tiletype_shape::RAMP: return get_constructed_ramp_str(tt); + case tiletype_shape::FORTIFICATION: return "CF"; + case tiletype_shape::STAIR_UP: return "Cu"; + case tiletype_shape::STAIR_DOWN: return "Cd"; + case tiletype_shape::STAIR_UPDOWN: return "Cx"; + default: + return "~"; + } + + return NULL; +} + static pair get_building_size(const df::building *b) { return pair(b->x2 - b->x1 + 1, b->y2 - b->y1 + 1); } @@ -461,56 +565,6 @@ static const char * get_furnace_str(df::building *b) { } } -static const char * get_construction_str(df::building *b) { - df::building_constructionst *cons = - virtual_cast(b); - if (!cons) - return "~"; - - switch (cons->type) { - case construction_type::Fortification: return "CF"; - case construction_type::Wall: return "Cw"; - case construction_type::Floor: return "Cf"; - case construction_type::UpStair: return "Cu"; - case construction_type::DownStair: return "Cd"; - case construction_type::UpDownStair: return "Cx"; - case construction_type::Ramp: return "Cr"; - case construction_type::TrackN: return "trackN"; - case construction_type::TrackS: return "trackS"; - case construction_type::TrackE: return "trackE"; - case construction_type::TrackW: return "trackW"; - case construction_type::TrackNS: return "trackNS"; - case construction_type::TrackNE: return "trackNE"; - case construction_type::TrackNW: return "trackNW"; - case construction_type::TrackSE: return "trackSE"; - case construction_type::TrackSW: return "trackSW"; - case construction_type::TrackEW: return "trackEW"; - case construction_type::TrackNSE: return "trackNSE"; - case construction_type::TrackNSW: return "trackNSW"; - case construction_type::TrackNEW: return "trackNEW"; - case construction_type::TrackSEW: return "trackSEW"; - case construction_type::TrackNSEW: return "trackNSEW"; - case construction_type::TrackRampN: return "trackrampN"; - case construction_type::TrackRampS: return "trackrampS"; - case construction_type::TrackRampE: return "trackrampE"; - case construction_type::TrackRampW: return "trackrampW"; - case construction_type::TrackRampNS: return "trackrampNS"; - case construction_type::TrackRampNE: return "trackrampNE"; - case construction_type::TrackRampNW: return "trackrampNW"; - case construction_type::TrackRampSE: return "trackrampSE"; - case construction_type::TrackRampSW: return "trackrampSW"; - case construction_type::TrackRampEW: return "trackrampEW"; - case construction_type::TrackRampNSE: return "trackrampNSE"; - case construction_type::TrackRampNSW: return "trackrampNSW"; - case construction_type::TrackRampNEW: return "trackrampNEW"; - case construction_type::TrackRampSEW: return "trackrampSEW"; - case construction_type::TrackRampNSEW: return "trackrampNSEW"; - case construction_type::NONE: - default: - return "~"; - } -} - static const char * get_trap_str(df::building *b) { df::building_trapst *trap = virtual_cast(b); if (!trap) @@ -622,6 +676,7 @@ static const char * get_build_keys(const df::coord &pos, bool at_center = static_cast(pos.x) == ctx.b->centerx && static_cast(pos.y) == ctx.b->centery; + // building_type::Construction is handled by the construction phase switch(ctx.b->getType()) { case building_type::Armorstand: return "a"; @@ -666,8 +721,6 @@ static const char * get_build_keys(const df::coord &pos, return "y"; case building_type::WindowGem: return "Y"; - case building_type::Construction: - return get_construction_str(ctx.b); case building_type::Shop: return do_block_building(ctx, "z", at_center); case building_type::AnimalTrap: @@ -1132,6 +1185,8 @@ static bool do_transform(color_ostream &out, smooth_get_tile_fn); add_processor(processors, opts, "dig", "carve", opts.carve, opts.engrave ? get_tile_carve : get_tile_carve_minimal); + add_processor(processors, opts, "build", "construct", opts.construct, + get_tile_construct, ensure_building); add_processor(processors, opts, "build", "build", opts.build, get_tile_build, ensure_building); add_processor(processors, opts, "place", "place", opts.place, diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua index 5c73be7c0..564962448 100644 --- a/plugins/lua/blueprint.lua +++ b/plugins/lua/blueprint.lua @@ -6,6 +6,7 @@ local utils = require('utils') local valid_phase_list = { 'dig', 'carve', + 'construct', 'build', 'place', 'zone', From ded5f483d664fc9f804a7f3ca40f04032fcb7793 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Wed, 12 Oct 2022 21:32:27 +0100 Subject: [PATCH 755/854] Fix issues in construction module --- docs/Lua API.rst | 3 ++- library/LuaApi.cpp | 2 +- library/include/modules/Constructions.h | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 75196e7b9..5cfd8cf09 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1912,7 +1912,8 @@ Constructions module * ``dfhack.constructions.insert(construction)`` - Properly inserts the given construction into the game. + Properly inserts the given construction into the game. Returns false and fails to + insert if there was already a construction at the position. Kitchen module -------------- diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 463cf3fcc..4a97a0502 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2264,6 +2264,7 @@ static const luaL_Reg dfhack_buildings_funcs[] = { static const LuaWrapper::FunctionReg dfhack_constructions_module[] = { WRAPM(Constructions, designateNew), + WRAPM(Constructions, insert), { NULL, NULL } }; @@ -2286,7 +2287,6 @@ static int constructions_findAtTile(lua_State *L) static const luaL_Reg dfhack_constructions_funcs[] = { { "designateRemove", constructions_designateRemove }, { "findAtTile", constructions_findAtTile }, - WRAPM(Constructions, insert), { NULL, NULL } }; diff --git a/library/include/modules/Constructions.h b/library/include/modules/Constructions.h index c7a1c048b..b152381c4 100644 --- a/library/include/modules/Constructions.h +++ b/library/include/modules/Constructions.h @@ -45,6 +45,8 @@ namespace Constructions DFHACK_EXPORT df::construction * findAtTile(df::coord pos); +DFHACK_EXPORT bool insert(df::construction * constr); + DFHACK_EXPORT bool designateNew(df::coord pos, df::construction_type type, df::item_type item = df::item_type::NONE, int mat_index = -1); From 754f1a2598167722c16e162026953e1ef10e5433 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 12 Oct 2022 13:33:29 -0700 Subject: [PATCH 756/854] update changelog --- docs/changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index f9b7accc5..37f3a1dd1 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -38,6 +38,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes ## Misc Improvements +- `blueprint`: new ``--smooth`` option for recording all smoothed floors and walls instead of just the ones that require smoothing for later carving +- `blueprint`: record built constructions in blueprints - `ls`: indent tag listings and wrap them in the right column for better readability - `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output. - `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down From 63e9192367c9b4b42c72cbff408c80a766df1d15 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 12 Oct 2022 14:13:09 -0700 Subject: [PATCH 757/854] Moves specate and pause-api to plugins/spectate/ --- plugins/CMakeLists.txt | 2 +- plugins/spectate/CMakeLists.txt | 8 ++ plugins/spectate/pause.cpp | 160 ++++++++++++++++++++++++++++ plugins/spectate/pause.h | 58 ++++++++++ plugins/{ => spectate}/spectate.cpp | 10 +- 5 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 plugins/spectate/CMakeLists.txt create mode 100644 plugins/spectate/pause.cpp create mode 100644 plugins/spectate/pause.h rename plugins/{ => spectate}/spectate.cpp (99%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index dc341b39d..c9438e04e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -163,7 +163,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(siege-engine siege-engine.cpp LINK_LIBRARIES lua) dfhack_plugin(sort sort.cpp LINK_LIBRARIES lua) dfhack_plugin(steam-engine steam-engine.cpp) - dfhack_plugin(spectate spectate.cpp) + add_subdirectory(spectate) dfhack_plugin(stockflow stockflow.cpp LINK_LIBRARIES lua) add_subdirectory(stockpiles) dfhack_plugin(stocks stocks.cpp) diff --git a/plugins/spectate/CMakeLists.txt b/plugins/spectate/CMakeLists.txt new file mode 100644 index 000000000..000291d34 --- /dev/null +++ b/plugins/spectate/CMakeLists.txt @@ -0,0 +1,8 @@ + +project(spectate) + +SET(SOURCES + spectate.cpp + pause.cpp) + +dfhack_plugin(${PROJECT_NAME} ${SOURCES} LINK_LIBRARIES lua) \ No newline at end of file diff --git a/plugins/spectate/pause.cpp b/plugins/spectate/pause.cpp new file mode 100644 index 000000000..bac3c1654 --- /dev/null +++ b/plugins/spectate/pause.cpp @@ -0,0 +1,160 @@ +#include "pause.h" +#include +#include +#include +#include +#include +#include + +#include + +using namespace DFHack; +using namespace Pausing; +using namespace df::enums; + +namespace pausing { + AnnouncementLock announcementLock("monitor"); + PlayerLock playerLock("monitor"); + + const size_t announcement_flag_arr_size = sizeof(decltype(df::announcements::flags)) / sizeof(df::announcement_flags); + bool state_saved = false; // indicates whether a restore state is ok + bool announcements_disabled = false; // indicates whether disable or restore was last enacted, could use a better name + bool saved_states[announcement_flag_arr_size]; // state to restore + bool locked_states[announcement_flag_arr_size]; // locked state (re-applied each frame) + bool allow_player_pause = true; // toggles player pause ability + + using df::global::ui; + using namespace df::enums; + struct player_pause_hook : df::viewscreen_dwarfmodest { + typedef df::viewscreen_dwarfmodest interpose_base; + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set* input)) { + if ((ui->main.mode == ui_sidebar_mode::Default) && !allow_player_pause) { + input->erase(interface_key::D_PAUSE); + } + INTERPOSE_NEXT(feed)(input); + } + }; + + IMPLEMENT_VMETHOD_INTERPOSE(player_pause_hook, feed); +} +using namespace pausing; + +std::unordered_set PlayerLock::locks; +std::unordered_set AnnouncementLock::locks; + +template +inline bool any_lock(Locks locks) { + return std::any_of(locks.begin(), locks.end(), [](Lock* lock) { return lock->isLocked(); }); +} + +template +inline bool only_lock(Locks locks, LockT* this_lock) { + return std::all_of(locks.begin(), locks.end(), [&](Lock* lock) { + if (lock == this_lock) { + return lock->isLocked(); + } + return !lock->isLocked(); + }); +} + +template +inline bool only_or_none_locked(Locks locks, LockT* this_lock) { + for (auto &L: locks) { + if (L == this_lock) { + continue; + } + if (L->isLocked()) { + return false; + } + } + return true; +} + +bool AnnouncementLock::captureState() { + if (only_or_none_locked(locks, this)) { + for (size_t i = 0; i < announcement_flag_arr_size; ++i) { + locked_states[i] = df::global::d_init->announcements.flags[i].bits.PAUSE; + } + return true; + } + return false; +} + +void AnnouncementLock::lock() { + Lock::lock(); + captureState(); +} + +bool AnnouncementLock::isAnyLocked() const { + return any_lock(locks); +} + +bool AnnouncementLock::isOnlyLocked() const { + return only_lock(locks, this); +} + +bool PlayerLock::isAnyLocked() const { + return any_lock(locks); +} + +bool PlayerLock::isOnlyLocked() const { + return only_lock(locks, this); +} + +bool World::DisableAnnouncementPausing() { + if (!announcementLock.isAnyLocked()) { + for (auto& flag : df::global::d_init->announcements.flags) { + flag.bits.PAUSE = false; + } + announcements_disabled = true; + } + return announcements_disabled; +} + +bool World::SaveAnnouncementSettings() { + if (!announcementLock.isAnyLocked()) { + for (size_t i = 0; i < announcement_flag_arr_size; ++i) { + saved_states[i] = df::global::d_init->announcements.flags[i].bits.PAUSE; + } + return true; + } + return false; +} + +bool World::RestoreAnnouncementSettings() { + if (!announcementLock.isAnyLocked() && state_saved) { + for (size_t i = 0; i < announcement_flag_arr_size; ++i) { + df::global::d_init->announcements.flags[i].bits.PAUSE = saved_states[i]; + } + announcements_disabled = false; + return true; + } + return false; +} + +bool World::EnablePlayerPausing() { + if (!playerLock.isAnyLocked()) { + allow_player_pause = true; + } + return allow_player_pause; +} + +bool World::DisablePlayerPausing() { + if (!playerLock.isAnyLocked()) { + allow_player_pause = false; + } + return !allow_player_pause; +} + +void World::Update() { + static bool did_once = false; + if (!did_once) { + did_once = true; + INTERPOSE_HOOK(player_pause_hook, feed).apply(); + } + if (announcementLock.isAnyLocked()) { + for (size_t i = 0; i < announcement_flag_arr_size; ++i) { + df::global::d_init->announcements.flags[i].bits.PAUSE = locked_states[i]; + } + } +} \ No newline at end of file diff --git a/plugins/spectate/pause.h b/plugins/spectate/pause.h new file mode 100644 index 000000000..dafc259d1 --- /dev/null +++ b/plugins/spectate/pause.h @@ -0,0 +1,58 @@ +#pragma once +#include +#include + +namespace DFHack { + //////////// + // Locking mechanisms for control over pausing + namespace Pausing + { + class Lock + { + bool locked = false; + std::string name; + public: + explicit Lock(const char* name) { this->name = name;}; + virtual ~Lock()= default; + virtual bool isAnyLocked() const = 0; + virtual bool isOnlyLocked() const = 0; + bool isLocked() const { return locked; } + virtual void lock() { locked = true; }; //simply locks the lock + void unlock() { locked = false; }; + }; + + // non-blocking lock resource used in conjunction with the announcement functions in World + class AnnouncementLock : public Lock + { + static std::unordered_set locks; + public: + explicit AnnouncementLock(const char* name): Lock(name) { locks.emplace(this); } + ~AnnouncementLock() override { locks.erase(this); } + bool captureState(); // captures the state of announcement settings, iff this is the only locked lock (note it does nothing if 0 locks are engaged) + void lock() override; // locks and attempts to capture state + bool isAnyLocked() const override; // returns true if any instance of AnnouncementLock is locked + bool isOnlyLocked() const override; // returns true if locked and no other instance is locked + }; + + // non-blocking lock resource used in conjunction with the Player pause functions in World + class PlayerLock : public Lock + { + static std::unordered_set locks; + public: + explicit PlayerLock(const char* name): Lock(name) { locks.emplace(this); } + ~PlayerLock() override { locks.erase(this); } + bool isAnyLocked() const override; // returns true if any instance of PlayerLock is locked + bool isOnlyLocked() const override; // returns true if locked and no other instance is locked + }; + } + namespace World { + bool DisableAnnouncementPausing(); // disable announcement pausing if all locks are open + bool SaveAnnouncementSettings(); // save current announcement pause settings if all locks are open + bool RestoreAnnouncementSettings(); // restore saved announcement pause settings if all locks are open + + bool EnablePlayerPausing(); // enable player pausing if all locks are open + bool DisablePlayerPausing(); // disable player pausing if all locks are open + + void Update(); + } +} \ No newline at end of file diff --git a/plugins/spectate.cpp b/plugins/spectate/spectate.cpp similarity index 99% rename from plugins/spectate.cpp rename to plugins/spectate/spectate.cpp index 2cf23df1d..fc305d33e 100644 --- a/plugins/spectate.cpp +++ b/plugins/spectate/spectate.cpp @@ -2,6 +2,8 @@ // Created by josh on 7/28/21. // +#include "pause.h" + #include "Core.h" #include #include @@ -31,6 +33,7 @@ REQUIRE_GLOBAL(pause_state); REQUIRE_GLOBAL(d_init); using namespace DFHack; +using namespace Pausing; using namespace df::enums; void onTick(color_ostream& out, void* tick); @@ -53,6 +56,9 @@ std::set job_tracker; std::map freq; std::default_random_engine RNG; + + + #define base 0.99 static const std::string CONFIG_KEY = std::string(plugin_name) + "/config"; @@ -80,12 +86,12 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector Date: Wed, 12 Oct 2022 14:15:11 -0700 Subject: [PATCH 758/854] Revert "Revises nopause in reveal with pause API in World module" This reverts commit 318dd4c7db33229a3517048967b3cee560aeb347. --- plugins/reveal.cpp | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index 3aba37b95..8d76bd54b 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -62,7 +62,6 @@ struct hideblock uint32_t x_max, y_max, z_max; vector hidesaved; bool nopause_state = false; -Pausing::PlayerLock* pause_lock = nullptr; enum revealstate { @@ -107,7 +106,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector & parameters) { if (parameters.size() == 1 && (parameters[0] == "0" || parameters[0] == "1")) { - if (parameters[0] == "0") { - if (nopause_state) { - pause_lock->unlock(); - if (!World::EnablePlayerPausing()) { - out.printerr("reveal/nopause: Player pausing is currently locked by another plugin / script. Unable to re-enable.\n"); - } - } + if (parameters[0] == "0") nopause_state = 0; - } else { - if (!nopause_state) { - pause_lock->unlock(); - if (!World::DisablePlayerPausing()) { - out.printerr("reveal/nopause: Player pausing is currently locked by another plugin / script. Unable to disable.\n"); - } - } + else nopause_state = 1; - } is_active = nopause_state || (revealed == REVEALED); out.print("nopause %sactivated.\n", (nopause_state ? "" : "de")); } @@ -490,7 +474,7 @@ command_result revflood(color_ostream &out, vector & params) return CR_WRONG_USAGE; } CoreSuspender suspend; - uint32_t xmax,ymax,zmax; + uint32_t x_max,y_max,z_max; if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); @@ -509,7 +493,7 @@ command_result revflood(color_ostream &out, vector & params) return CR_FAILURE; } int32_t cx, cy, cz; - Maps::getSize(xmax, ymax, zmax); + Maps::getSize(x_max,y_max,z_max); Gui::getCursorCoords(cx,cy,cz); if(cx == -30000) @@ -527,7 +511,7 @@ command_result revflood(color_ostream &out, vector & params) return CR_FAILURE; } // hide all tiles, flush cache - Maps::getSize(xmax, ymax, zmax); + Maps::getSize(x_max,y_max,z_max); for(size_t i = 0; i < world->map.map_blocks.size(); i++) { From d6752889692756aa7d0971b1eb9fbf786867700b Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 12 Oct 2022 14:18:32 -0700 Subject: [PATCH 759/854] Manually reverts Pause-api implementation in core/modules --- library/Core.cpp | 3 - library/include/modules/World.h | 56 -------------- library/modules/World.cpp | 133 -------------------------------- 3 files changed, 192 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index cf47e4ec1..87f78f56c 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1913,9 +1913,6 @@ void Core::doUpdate(color_ostream &out, bool first_update) // Execute per-frame handlers onUpdate(out); - // Execute per-frame World module maintenance, after plugins and lua timers - World::Update(); - d->last_autosave_request = df::global::ui->main.autosave_request; d->was_load_save = is_load_save; diff --git a/library/include/modules/World.h b/library/include/modules/World.h index 5b87dd4cf..4cba97692 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -62,47 +62,6 @@ namespace DFHack }; class DFContextShared; - //////////// - // Locking mechanisms for control over pausing - namespace Pausing - { - class Lock - { - bool locked = false; - std::string name; - public: - explicit Lock(const char* name) { this->name = name;}; - virtual ~Lock()= default; - virtual bool isAnyLocked() const = 0; - virtual bool isOnlyLocked() const = 0; - bool isLocked() const { return locked; } - void lock() { locked = true; }; - void unlock() { locked = false; }; - }; - - // non-blocking lock resource used in conjunction with the announcement functions in World - class AnnouncementLock : public Lock - { - static std::unordered_set locks; - public: - explicit AnnouncementLock(const char* name): Lock(name) { locks.emplace(this); } - ~AnnouncementLock() override { locks.erase(this); } - bool isAnyLocked() const override; // returns true if any instance of AnnouncementLock is locked - bool isOnlyLocked() const override; // returns true if locked and no other instance is locked - }; - - // non-blocking lock resource used in conjunction with the Player pause functions in World - class PlayerLock : public Lock - { - static std::unordered_set locks; - public: - explicit PlayerLock(const char* name): Lock(name) { locks.emplace(this); } - ~PlayerLock() override { locks.erase(this); } - bool isAnyLocked() const override; // returns true if any instance of PlayerLock is locked - bool isOnlyLocked() const override; // returns true if locked and no other instance is locked - }; - } - /** * The World module * \ingroup grp_modules @@ -115,21 +74,6 @@ namespace DFHack ///true if paused, false if not DFHACK_EXPORT void SetPauseState(bool paused); - // todo: prevent anything else from allocating/deleting? - // Acquire & Release pause locks controlling player pausing and announcement pause settings - DFHACK_EXPORT Pausing::AnnouncementLock* AcquireAnnouncementPauseLock(const char*); - DFHACK_EXPORT Pausing::PlayerLock* AcquirePlayerPauseLock(const char*); - DFHACK_EXPORT void ReleasePauseLock(Pausing::Lock*); - - DFHACK_EXPORT bool DisableAnnouncementPausing(); // disable announcement pausing if all locks are open - DFHACK_EXPORT bool SaveAnnouncementSettings(); // save current announcement pause settings if all locks are open - DFHACK_EXPORT bool RestoreAnnouncementSettings(); // restore saved announcement pause settings if all locks are open - - DFHACK_EXPORT bool EnablePlayerPausing(); // enable player pausing if all locks are open - DFHACK_EXPORT bool DisablePlayerPausing(); // disable player pausing if all locks are open - - void Update(); - DFHACK_EXPORT uint32_t ReadCurrentTick(); DFHACK_EXPORT uint32_t ReadCurrentYear(); DFHACK_EXPORT uint32_t ReadCurrentMonth(); diff --git a/library/modules/World.cpp b/library/modules/World.cpp index ae959039c..3f6ccb2c7 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -59,7 +59,6 @@ using namespace std; #include "PluginManager.h" using namespace DFHack; -using namespace Pausing; using namespace df::enums; using df::global::world; @@ -75,138 +74,6 @@ void World::SetPauseState(bool paused) DF_GLOBAL_VALUE(pause_state, dummy) = paused; } -std::unordered_set PlayerLock::locks; -std::unordered_set AnnouncementLock::locks; - -template -inline bool any_lock(Locks locks) { - return std::any_of(locks.begin(), locks.end(), [](Lock* lock) { return lock->isLocked(); }); -} - -template -inline bool only_lock(Locks locks, LockT* this_lock) { - return std::all_of(locks.begin(), locks.end(), [&](Lock* lock) { - if (lock == this_lock) { - return lock->isLocked(); - } - return !lock->isLocked(); - }); -} - -bool AnnouncementLock::isAnyLocked() const { - return any_lock(locks); -} - -bool AnnouncementLock::isOnlyLocked() const { - return only_lock(locks, this); -} - -bool PlayerLock::isAnyLocked() const { - return any_lock(locks); -} - -bool PlayerLock::isOnlyLocked() const { - return only_lock(locks, this); -} - -namespace pausing { - AnnouncementLock announcementLock("monitor"); - PlayerLock playerLock("monitor"); - - const size_t array_size = sizeof(decltype(df::announcements::flags)) / sizeof(df::announcement_flags); - bool state_saved = false; // indicates whether a restore state is ok - bool announcements_disabled = false; // indicates whether disable or restore was last enacted, could use a better name - bool saved_states[array_size]; // state to restore - bool locked_states[array_size]; // locked state (re-applied each frame) - bool allow_player_pause = true; // toggles player pause ability - - using df::global::ui; - using namespace df::enums; - struct player_pause_hook : df::viewscreen_dwarfmodest { - typedef df::viewscreen_dwarfmodest interpose_base; - DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set* input)) { - if ((ui->main.mode == ui_sidebar_mode::Default) && !allow_player_pause) { - input->erase(interface_key::D_PAUSE); - } - INTERPOSE_NEXT(feed)(input); - } - }; - - IMPLEMENT_VMETHOD_INTERPOSE(player_pause_hook, feed); -} -using namespace pausing; - -AnnouncementLock* World::AcquireAnnouncementPauseLock(const char* name) { - return new AnnouncementLock(name); -} - -PlayerLock* World::AcquirePlayerPauseLock(const char* name) { - return new PlayerLock(name); -} - -void World::ReleasePauseLock(Lock* lock){ - delete lock; -} - -bool World::DisableAnnouncementPausing() { - if (!announcementLock.isAnyLocked()) { - for (auto& flag : df::global::d_init->announcements.flags) { - flag.bits.PAUSE = false; - } - announcements_disabled = true; - } - return announcements_disabled; -} - -bool World::SaveAnnouncementSettings() { - if (!announcementLock.isAnyLocked()) { - for (size_t i = 0; i < array_size; ++i) { - saved_states[i] = df::global::d_init->announcements.flags[i].bits.PAUSE; - } - return true; - } - return false; -} - -bool World::RestoreAnnouncementSettings() { - if (!announcementLock.isAnyLocked() && state_saved) { - for (size_t i = 0; i < array_size; ++i) { - df::global::d_init->announcements.flags[i].bits.PAUSE = saved_states[i]; - } - announcements_disabled = false; - return true; - } - return false; -} - -bool World::EnablePlayerPausing() { - if (!playerLock.isAnyLocked()) { - allow_player_pause = true; - } - return allow_player_pause; -} - -bool World::DisablePlayerPausing() { - if (!playerLock.isAnyLocked()) { - allow_player_pause = false; - } - return !allow_player_pause; -} - -void World::Update() { - static bool did_once = false; - if (!did_once) { - did_once = true; - INTERPOSE_HOOK(player_pause_hook, feed).apply(); - } - if (announcementLock.isAnyLocked()) { - for (size_t i = 0; i < array_size; ++i) { - df::global::d_init->announcements.flags[i].bits.PAUSE = locked_states[i]; - } - } -} - - uint32_t World::ReadCurrentYear() { return DF_GLOBAL_VALUE(cur_year, 0); From cd4c6489dcf9399182080e3b8f7696e0950a2618 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 12 Oct 2022 14:39:49 -0700 Subject: [PATCH 760/854] Changed spectate auto-disengage to only trigger when unpaused --- plugins/spectate/spectate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/spectate/spectate.cpp b/plugins/spectate/spectate.cpp index fc305d33e..1148e26a4 100644 --- a/plugins/spectate/spectate.cpp +++ b/plugins/spectate/spectate.cpp @@ -168,7 +168,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { // dismiss announcement popup(s) Gui::getCurViewscreen(true)->feed_key(interface_key::CLOSE_MEGA_ANNOUNCEMENT); } - if (disengage_enabled) { + if (disengage_enabled && !World::ReadPauseState()) { if (our_dorf && our_dorf->id != df::global::ui->follow_unit) { plugin_enable(out, false); } From 23c2d14b4ab4968fa82b21d55c795a567f97fab9 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 12 Oct 2022 14:40:21 -0700 Subject: [PATCH 761/854] Updated documentation/changelog --- docs/changelog.txt | 13 +++++-------- docs/plugins/spectate.rst | 11 +++++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index e1a53fe3c..e4166c8e6 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -64,12 +64,17 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. - `seedwatch`: ``seedwatch all`` now adds all plants with seeds to the watchlist, not just the "basic" crops. - `spectate`: ``spectate auto-unpause`` is a new feature that will auto-dismiss pause events when enabled. This does not affect the player's ability to pause at a whim. +- `spectate`: ``spectate auto-disengage`` is a new feature that will auto-disengage the plugin when the player interacts with the game while unpaused +- `spectate`: ``spectate focus-jobs`` is a new feature that allows the plugin to not always follow a job, so it can trail a dwarf after they finish a job +- `spectate`: ``spectate tick-threshold`` is a new feature that allows the player to change how long to follow a dwarf +- `spectate`: added persistent configuration of the plugin settings - UX: You can now move the cursor around in DFHack text fields in ``gui/`` scripts (e.g. `gui/blueprint`, `gui/quickfort`, or `gui/gm-editor`). You can move the cursor by clicking where you want it to go with the mouse or using the Left/Right arrow keys. Ctrl+Left/Right will move one word at a time, and Alt+Left/Right will move to the beginning/end of the text. - UX: You can now click on the hotkey hint text in many ``gui/`` script windows to activate the hotkey, like a button. Not all scripts have been updated to use the clickable widget yet, but you can try it in `gui/blueprint` or `gui/quickfort`. - UX: Label widget scroll icons are replaced with scrollbars that represent the percentage of text on the screen and move with the position of the visible text, just like web browser scrollbars. - `quickfort`: `Dreamfort ` blueprint set improvements: set traffic designations to encourage dwarves to eat cooked food instead of raw ingredients ## Documentation +- `spectate`: improved documentation of features and functionality - Added `modding-guide` - Update all DFHack tool documentation (300+ pages) with standard syntax formatting, usage examples, and overall clarified text. - Group DFHack tools by `tag ` so similar tools are grouped and easy to find @@ -81,14 +86,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Removed ``Windows`` module (C++-only) - unused. - ``Constructions`` module (C++-only): removed ``t_construction``, ``isValid()``, ``getCount()``, ``getConstruction()``, and ``copyConstruction()``. Access ``world.constructions`` directly instead. - ``Gui::getSelectedItem()``, ``Gui::getAnyItem()``: added support for the artifacts screen -- Added functions for manipulating pause abilities, to the World module. - - Added ``World::AcquireAnnouncementPauseLock()`` acquires a lock object to lock Announcement pause API calls - - Added ``World::AcquirePlayerPauseLock()`` acquires a lock object to lock Player pause API calls - - Added ``World::ReleasePauseLock()`` to release acquired locks on plugin shutdown - - Added ``World::DisableAnnouncementPausing()`` sets all announcement pause settings to off - - Added ``World::SaveAnnouncementSettings()`` and ``World::RestoreAnnouncementSettings()`` saves/restores the announcement pause settings - - Added ``World::DisablePlayerPausing()`` and ``World::EnablePlayerPausing()`` disables/enables the player's ability to pause the game - ## Lua - History: added ``dfhack.getCommandHistory(history_id, history_filename)`` and ``dfhack.addCommandToHistory(history_id, history_filename, command)`` so gui scripts can access a commandline history without requiring a terminal. diff --git a/docs/plugins/spectate.rst b/docs/plugins/spectate.rst index 48bdd90bd..eea85d3da 100644 --- a/docs/plugins/spectate.rst +++ b/docs/plugins/spectate.rst @@ -23,6 +23,9 @@ To set features that toggle between two states, use {0,1} to specify which state the feature should be in. Anything else will take any positive value. +Changes to plugin settings will be saved per world. Whether the plugin itself +is enabled or not is not saved. + Examples -------- @@ -42,7 +45,7 @@ Options :no option: Show plugin status. :tick-threshold: Set the plugin's tick interval for changing the followed dwarf. - Acts as a maximum wait time when used with focus-jobs. -:focus-jobs: Toggle whether the plugin should always be following a job. -:auto-unpause: Toggle auto-dismissal of game pause events. -:auto-disengage: Toggle auto-disengagement of plugin through player intervention. + Acts as a maximum follow time when used with focus-jobs enabled. (default: 50) +:focus-jobs: Toggle whether the plugin should always be following a job. (default: 0) +:auto-unpause: Toggle auto-dismissal of game pause events. (default: 0) +:auto-disengage: Toggle auto-disengagement of plugin through player intervention while unpaused. (default: 0) From f54d37c8e7b5623fed5c3eb0db4757bf163ff09c Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 12 Oct 2022 14:42:04 -0700 Subject: [PATCH 762/854] Moved spectate changelog entries to new section --- docs/changelog.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index ae97bd203..7bb4bd9c7 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -44,8 +44,14 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - UX: List widgets now have mouse-interactive scrollbars - UX: You can now hold down the mouse button on a scrollbar to make it scroll multiple times. - UX: You can now drag the scrollbar to scroll to a specific spot +- `spectate`: ``spectate auto-unpause`` is a new feature that will auto-dismiss pause events when enabled. This does not affect the player's ability to pause at a whim. +- `spectate`: ``spectate auto-disengage`` is a new feature that will auto-disengage the plugin when the player interacts with the game while unpaused +- `spectate`: ``spectate focus-jobs`` is a new feature that allows the plugin to not always follow a job, so it can trail a dwarf after they finish a job +- `spectate`: ``spectate tick-threshold`` is a new feature that allows the player to change how long to follow a dwarf +- `spectate`: added persistent configuration of the plugin settings ## Documentation +- `spectate`: improved documentation of features and functionality ## API @@ -83,18 +89,12 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `orders`: added useful library of manager orders. see them with ``orders list`` and import them with, for example, ``orders import library/basic`` - `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. - `seedwatch`: ``seedwatch all`` now adds all plants with seeds to the watchlist, not just the "basic" crops. -- `spectate`: ``spectate auto-unpause`` is a new feature that will auto-dismiss pause events when enabled. This does not affect the player's ability to pause at a whim. -- `spectate`: ``spectate auto-disengage`` is a new feature that will auto-disengage the plugin when the player interacts with the game while unpaused -- `spectate`: ``spectate focus-jobs`` is a new feature that allows the plugin to not always follow a job, so it can trail a dwarf after they finish a job -- `spectate`: ``spectate tick-threshold`` is a new feature that allows the player to change how long to follow a dwarf -- `spectate`: added persistent configuration of the plugin settings - UX: You can now move the cursor around in DFHack text fields in ``gui/`` scripts (e.g. `gui/blueprint`, `gui/quickfort`, or `gui/gm-editor`). You can move the cursor by clicking where you want it to go with the mouse or using the Left/Right arrow keys. Ctrl+Left/Right will move one word at a time, and Alt+Left/Right will move to the beginning/end of the text. - UX: You can now click on the hotkey hint text in many ``gui/`` script windows to activate the hotkey, like a button. Not all scripts have been updated to use the clickable widget yet, but you can try it in `gui/blueprint` or `gui/quickfort`. - UX: Label widget scroll icons are replaced with scrollbars that represent the percentage of text on the screen and move with the position of the visible text, just like web browser scrollbars. - `quickfort`: `Dreamfort ` blueprint set improvements: set traffic designations to encourage dwarves to eat cooked food instead of raw ingredients ## Documentation -- `spectate`: improved documentation of features and functionality - Added `modding-guide` - Update all DFHack tool documentation (300+ pages) with standard syntax formatting, usage examples, and overall clarified text. - Group DFHack tools by `tag ` so similar tools are grouped and easy to find From e5b8de55efc2ae4d780cbf357952996df0d8f900 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 12 Oct 2022 14:44:51 -0700 Subject: [PATCH 763/854] Adds missing newlines --- plugins/spectate/CMakeLists.txt | 2 +- plugins/spectate/pause.cpp | 2 +- plugins/spectate/pause.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/spectate/CMakeLists.txt b/plugins/spectate/CMakeLists.txt index 000291d34..5ba1ed9cf 100644 --- a/plugins/spectate/CMakeLists.txt +++ b/plugins/spectate/CMakeLists.txt @@ -5,4 +5,4 @@ SET(SOURCES spectate.cpp pause.cpp) -dfhack_plugin(${PROJECT_NAME} ${SOURCES} LINK_LIBRARIES lua) \ No newline at end of file +dfhack_plugin(${PROJECT_NAME} ${SOURCES} LINK_LIBRARIES lua) diff --git a/plugins/spectate/pause.cpp b/plugins/spectate/pause.cpp index bac3c1654..bb3e90ee0 100644 --- a/plugins/spectate/pause.cpp +++ b/plugins/spectate/pause.cpp @@ -157,4 +157,4 @@ void World::Update() { df::global::d_init->announcements.flags[i].bits.PAUSE = locked_states[i]; } } -} \ No newline at end of file +} diff --git a/plugins/spectate/pause.h b/plugins/spectate/pause.h index dafc259d1..98d703998 100644 --- a/plugins/spectate/pause.h +++ b/plugins/spectate/pause.h @@ -55,4 +55,4 @@ namespace DFHack { void Update(); } -} \ No newline at end of file +} From aa83aa4e7161bf349dfb71131bdbe5d94108a01f Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 12 Oct 2022 14:51:36 -0700 Subject: [PATCH 764/854] handle construct phase in ecosystem tests --- .../test/ecosystem/golden/transform-build.csv | 40 ++++++------- .../ecosystem/golden/transform-construct.csv | 28 +++++++++ .../test/ecosystem/in/buildings-build.csv | 60 +++++++++---------- .../test/ecosystem/in/buildings-construct.csv | 21 +++++++ .../test/ecosystem/in/transform-build.csv | 13 ---- .../test/ecosystem/in/transform-construct.csv | 23 +++++++ test/quickfort/ecosystem.lua | 1 + 7 files changed, 123 insertions(+), 63 deletions(-) create mode 100644 data/blueprints/library/test/ecosystem/golden/transform-construct.csv create mode 100644 data/blueprints/library/test/ecosystem/in/buildings-construct.csv create mode 100644 data/blueprints/library/test/ecosystem/in/transform-construct.csv diff --git a/data/blueprints/library/test/ecosystem/golden/transform-build.csv b/data/blueprints/library/test/ecosystem/golden/transform-build.csv index 2cab6e804..74434544a 100644 --- a/data/blueprints/library/test/ecosystem/golden/transform-build.csv +++ b/data/blueprints/library/test/ecosystem/golden/transform-build.csv @@ -1,28 +1,28 @@ #build label(build) start(14;14) -,trackNS,trackE,,trackW,trackS,trackN,,gs(1x2),ga(2x1),,gx(1x2),gw(1x2),,gw(1x2),gx(1x2),gd(2x1),,gs(1x2),,trackN,trackS,trackE,,trackW,trackNS -trackEW,,trackSE,,trackSW,trackNE,trackNW,,,gd(2x1),,,,,,,ga(2x1),,,,trackNE,trackNW,trackSE,,trackSW,,trackEW -trackS,trackSE,,,trackNSE,trackNSW,trackEW,,Mrsssqq(2x1),,,Msh,,,,,Msk,Mrsqq(2x1),,,trackEW,trackNSE,trackNSW,,,trackSW,trackS +,~,~,,~,~,~,,gs(1x2),ga(2x1),,gx(1x2),gw(1x2),,gw(1x2),gx(1x2),gd(2x1),,gs(1x2),,~,~,~,,~,~ +~,,~,,~,~,~,,,gd(2x1),,,,,,,ga(2x1),,,,~,~,~,,~,,~ +~,~,,,~,~,~,,Mrsssqq(2x1),,,Msh,,,,,Msk,Mrsqq(2x1),,,~,~,~,,,~,~ ,,,,,,,,CSdddaaaa,,Msk,,Mw,,Mw,,,Msh,CSddddaaaa -trackN,trackNE,trackSEW,,,trackSEW,trackNEW,,CSa,,Mrss(1x2),Mw,,,,Mw,Mrss(1x2),,CSa,,trackNEW,trackSEW,,,trackSEW,trackNW,trackN -trackE,trackSW,trackNEW,,trackNSE,,trackNSEW,,,Msm,,,Mhs(1x2),,Mhs(1x2),,,Msm,,,trackNSEW,,trackNSW,,trackNEW,trackSE,trackW -trackW,trackNW,trackNS,,trackNSW,trackNSEW,,,Msu,,Mws,,,,,,Mws,,Msu,,,trackNSEW,trackNSE,,trackNS,trackNE,trackE +~,~,~,,,~,~,,CSa,,Mrss(1x2),Mw,,,,Mw,Mrss(1x2),,CSa,,~,~,,,~,~,~ +~,~,~,,~,,~,,,Msm,,,Mhs(1x2),,Mhs(1x2),,,Msm,,,~,,~,,~,~,~ +~,~,~,,~,~,,,Msu,,Mws,,,,,,Mws,,Msu,,,~,~,,~,~,~ ,,,,,,,,,Mws,,Mh(2x1),,,Mh(2x1),,,Mws gs(2x1),,Mrqq(1x2),CSddaaaa,CSa,,Msh,,,,,,,,,,,,,,,Msk,CSa,CSddaaaa,Mrqq(1x2),gs(2x1) -gw(1x2),gx(1x2),,,,Msk,,Mw,,,trackrampNW,trackrampNS,trackrampN,,trackrampN,trackrampNS,trackrampNE,,,Mw,,,Msh,,,gx(1x2),gw(1x2) -,,,Msm,Mrs(2x1),,Mw,,,trackrampNW,,trackrampNSE,trackrampNSW,,trackrampNSE,trackrampNSW,,trackrampNE,,,Mw,Mrsss(2x1),,Msm -gd(2x1),,Msu,,Mws,,,Mhs(1x2),,trackrampEW,trackrampSEW,,trackrampNSEW,,trackrampNSEW,,trackrampSEW,trackrampEW,,Mhs(1x2),,,Mws,,Msu,ga(2x1) -ga(2x1),,,Mws,,Mh(2x1),,,,trackrampW,trackrampNEW,trackrampNSEW,,,,trackrampNSEW,trackrampNEW,trackrampE,,,Mh(2x1),,,Mws,,gd(2x1) +gw(1x2),gx(1x2),,,,Msk,,Mw,,,~,~,~,,~,~,~,,,Mw,,,Msh,,,gx(1x2),gw(1x2) +,,,Msm,Mrs(2x1),,Mw,,,~,,~,~,,~,~,,~,,,Mw,Mrsss(2x1),,Msm +gd(2x1),,Msu,,Mws,,,Mhs(1x2),,~,~,,~,,~,,~,~,,Mhs(1x2),,,Mws,,Msu,ga(2x1) +ga(2x1),,,Mws,,Mh(2x1),,,,~,~,~,,,,~,~,~,,,Mh(2x1),,,Mws,,gd(2x1) -ga(2x1),,,Mws,,Mh(2x1),,Mhs(1x2),,trackrampW,trackrampSEW,trackrampNSEW,,,,trackrampNSEW,trackrampSEW,trackrampE,,Mhs(1x2),Mh(2x1),,,Mws,,gd(2x1) -gd(2x1),,,,Mws,,,,,trackrampEW,trackrampNEW,,trackrampNSEW,,trackrampNSEW,,trackrampNEW,trackrampEW,,,,,Mws,,,ga(2x1) -gx(1x2),gw(1x2),Msm,,Mrs(2x1),,Mw,,,trackrampSW,,trackrampNSE,trackrampNSW,,trackrampNSE,trackrampNSW,,trackrampSE,,,Mw,Mrsss(2x1),,,Msm,gw(1x2),gx(1x2) -,,Mrssqq(1x2),Msu,,Msk,,Mw,,,trackrampSW,trackrampNS,trackrampS,,trackrampS,trackrampNS,trackrampSE,,,Mw,,,Msh,Msu,Mrssqq(1x2) +ga(2x1),,,Mws,,Mh(2x1),,Mhs(1x2),,~,~,~,,,,~,~,~,,Mhs(1x2),Mh(2x1),,,Mws,,gd(2x1) +gd(2x1),,,,Mws,,,,,~,~,,~,,~,,~,~,,,,,Mws,,,ga(2x1) +gx(1x2),gw(1x2),Msm,,Mrs(2x1),,Mw,,,~,,~,~,,~,~,,~,,,Mw,Mrsss(2x1),,,Msm,gw(1x2),gx(1x2) +,,Mrssqq(1x2),Msu,,Msk,,Mw,,,~,~,~,,~,~,~,,,Mw,,,Msh,Msu,Mrssqq(1x2) gs(2x1),,,CSdaaaa,CSa,,Msh,,,,,,,,,,,,,,,Msk,CSa,CSdaaaa,,gs(2x1) ,,,,,,,,,Mws,,Mh(2x1),,,Mh(2x1),,,Mws -trackW,trackSW,trackNS,,trackNSW,trackNSEW,,,,,Mws,,Mhs(1x2),,Mhs(1x2),,Mws,,,,,trackNSEW,trackNSE,,trackNS,trackSE,trackE -trackE,trackNW,trackSEW,,trackNSE,,trackNSEW,,Msm,,Mr(1x2),,,,,,Mr(1x2),,Msm,,trackNSEW,,trackNSW,,trackSEW,trackNE,trackW -trackS,trackSE,trackNEW,,,trackNEW,trackSEW,,CSa,Msu,,Mw,,,,Mw,,Msu,CSa,,trackSEW,trackNEW,,,trackNEW,trackSW,trackS +~,~,~,,~,~,,,,,Mws,,Mhs(1x2),,Mhs(1x2),,Mws,,,,,~,~,,~,~,~ +~,~,~,,~,,~,,Msm,,Mr(1x2),,,,,,Mr(1x2),,Msm,,~,,~,,~,~,~ +~,~,~,,,~,~,,CSa,Msu,,Mw,,,,Mw,,Msu,CSa,,~,~,,,~,~,~ ,,,,,,,,CSdddaaaa,,Msk,,Mw,,Mw,,,Msh,CSddddaaaa -trackN,trackNE,,,trackNSE,trackNSW,trackEW,,Mrsssqq(2x1),,,Msh,,,,,Msk,Mrsqq(2x1),,,trackEW,trackNSE,trackNSW,,,trackNW,trackN -trackEW,,trackNE,,trackNW,trackSE,trackSW,,gs(1x2),gd(2x1),,gw(1x2),gx(1x2),,gx(1x2),gw(1x2),ga(2x1),,gs(1x2),,trackSE,trackSW,trackNE,,trackNW,,trackEW -,trackNS,trackE,,trackW,trackN,trackS,,,ga(2x1),,,,,,,gd(2x1),,,,trackS,trackN,trackE,,trackW,trackNS +~,~,,,~,~,~,,Mrsssqq(2x1),,,Msh,,,,,Msk,Mrsqq(2x1),,,~,~,~,,,~,~ +~,,~,,~,~,~,,gs(1x2),gd(2x1),,gw(1x2),gx(1x2),,gx(1x2),gw(1x2),ga(2x1),,gs(1x2),,~,~,~,,~,,~ +,~,~,,~,~,~,,,ga(2x1),,,,,,,gd(2x1),,,,~,~,~,,~,~ diff --git a/data/blueprints/library/test/ecosystem/golden/transform-construct.csv b/data/blueprints/library/test/ecosystem/golden/transform-construct.csv new file mode 100644 index 000000000..9620c96c1 --- /dev/null +++ b/data/blueprints/library/test/ecosystem/golden/transform-construct.csv @@ -0,0 +1,28 @@ +#build label(construct) start(14;14) +,trackNS,trackE,,trackW,trackS,trackN,,,,,,,,,,,,,,trackN,trackS,trackE,,trackW,trackNS +trackEW,,trackSE,,trackSW,trackNE,trackNW,,,,,,,,,,,,,,trackNE,trackNW,trackSE,,trackSW,,trackEW +trackS,trackSE,,,trackNSE,trackNSW,trackEW,,,,,,,,,,,,,,trackEW,trackNSE,trackNSW,,,trackSW,trackS + +trackN,trackNE,trackSEW,,,trackSEW,trackNEW,,,,,,,,,,,,,,trackNEW,trackSEW,,,trackSEW,trackNW,trackN +trackE,trackSW,trackNEW,,trackNSE,,trackNSEW,,,,,,,,,,,,,,trackNSEW,,trackNSW,,trackNEW,trackSE,trackW +trackW,trackNW,trackNS,,trackNSW,trackNSEW,,,,,,,,,,,,,,,,trackNSEW,trackNSE,,trackNS,trackNE,trackE + + +,,,,,,,,,,trackrampNW,trackrampNS,trackrampN,,trackrampN,trackrampNS,trackrampNE +,,,,,,,,,trackrampNW,,trackrampNSE,trackrampNSW,,trackrampNSE,trackrampNSW,,trackrampNE +,,,,,,,,,trackrampEW,trackrampSEW,,trackrampNSEW,,trackrampNSEW,,trackrampSEW,trackrampEW +,,,,,,,,,trackrampW,trackrampNEW,trackrampNSEW,,,,trackrampNSEW,trackrampNEW,trackrampE + +,,,,,,,,,trackrampW,trackrampSEW,trackrampNSEW,,,,trackrampNSEW,trackrampSEW,trackrampE +,,,,,,,,,trackrampEW,trackrampNEW,,trackrampNSEW,,trackrampNSEW,,trackrampNEW,trackrampEW +,,,,,,,,,trackrampSW,,trackrampNSE,trackrampNSW,,trackrampNSE,trackrampNSW,,trackrampSE +,,,,,,,,,,trackrampSW,trackrampNS,trackrampS,,trackrampS,trackrampNS,trackrampSE + + +trackW,trackSW,trackNS,,trackNSW,trackNSEW,,,,,,,,,,,,,,,,trackNSEW,trackNSE,,trackNS,trackSE,trackE +trackE,trackNW,trackSEW,,trackNSE,,trackNSEW,,,,,,,,,,,,,,trackNSEW,,trackNSW,,trackSEW,trackNE,trackW +trackS,trackSE,trackNEW,,,trackNEW,trackSEW,,,,,,,,,,,,,,trackSEW,trackNEW,,,trackNEW,trackSW,trackS + +trackN,trackNE,,,trackNSE,trackNSW,trackEW,,,,,,,,,,,,,,trackEW,trackNSE,trackNSW,,,trackNW,trackN +trackEW,,trackNE,,trackNW,trackSE,trackSW,,,,,,,,,,,,,,trackSE,trackSW,trackNE,,trackNW,,trackEW +,trackNS,trackE,,trackW,trackN,trackS,,,,,,,,,,,,,,trackS,trackN,trackE,,trackW,trackNS diff --git a/data/blueprints/library/test/ecosystem/in/buildings-build.csv b/data/blueprints/library/test/ecosystem/in/buildings-build.csv index 89e066e80..f48457c0a 100644 --- a/data/blueprints/library/test/ecosystem/in/buildings-build.csv +++ b/data/blueprints/library/test/ecosystem/in/buildings-build.csv @@ -1,33 +1,33 @@ #build label(build) -a,Mg,,CS,trackN -b,Mh(1x1),S,CSa,trackS,,,,,,Mw,,,wm,,,wp -c,Mhs(1x1),m,CSaa,trackE,,,,,,,,,,,,,,,,,D -n,Mv,v,CSaaa,trackW,,Msu -,Mr(1x1),j,CSaaaa,trackNS,,,,,,Mws,,,wu,,,ew -d,Mrq(1x1),A,CSd,trackNE,,,Msk -,Mrqq(1x1),R,CSda,trackNW -l,Mrqqq(1x1),N,CSdaa,trackSE,,Msm,,,,we,,,wn,,,es,,,,,k -x,Mrqqqq(1x1),~h,CSdaaa,trackSW -H,Mrs(1x1),~a,CSdaaaa,trackEW,,,Msh -W,Mrsq(1x1),~c,CSdd,trackNSE,,,,,,wq,,,wr,,,el -G,Mrsqq(1x1),F,CSdda,trackNSW -B,Mrsqqq(1x1),o(1x1),CSddaa,trackNEW,,,,,,,,,,,,,,,,,ws -~b,Mrsqqqq(1x1),Cw,CSddaaa,trackSEW,,,,,,wM,,,wt,,,eg -f,Mrss(1x1),Cf,CSddaaaa,trackNSEW -h,Mrssq(1x1),Cr,CSddd,trackrampN -r,Mrssqq(1x1),Cu,CSddda,trackrampS,,,,,,wo,,,wl,,,ea,,,,gx(1x2),gx(1x2) -s,Mrssqqq(1x1),Cd,CSdddaa,trackrampE -~s,Mrssqqqq(1x1),Cx,CSdddaaa,trackrampW,,,,,,,,,,,,,,gd(2x1),,gs(2x1),,ga(2x1) -t,Mrsss(1x1),CF,CSdddaaaa,trackrampNS,,,,,,wk,,,ww,,,ek,,gd(2x1),,gs(2x1),,ga(2x1) -gs(1x1),Mrsssq(1x1),,CSdddd,trackrampNE,,,,,,,,,,,,,,,,gw(1x2),gw(1x2) -ga(1x1),Mrsssqq(1x1),,CSdddda,trackrampNW -gd(1x1),Mrsssqqq(1x1),,CSddddaa,trackrampSE,,,,,,wb,,,wz,,,en -gw(1x1),Mrsssqqqq(1x1),,CSddddaaa,trackrampSW,,,,,,,,,,,,,,Mh(2x1),,Mh(2x1),,Mhs(1x2),Mhs(1x2) -gx(1x1),,,CSddddaaaa,trackrampEW,,,,,,,,,,,,,,Mh(2x1),,Mh(2x1) -,,,Ts,trackrampNSE,,,,,,wc,,,wh,,,ib,,Mr(1x2),Mr(1x2),Mrs(2x1),,Mhs(1x2),Mhs(1x2) -y,,,Tw,trackrampNSW,,,,,,,,,,,,,,,,Mrs(2x1) -Y,,,Tl,trackrampNEW,,,,,,,,,,,,,,Mr(1x2),Mr(1x2),Mrsq(2x1),,Mrsq(2x1) -,,,Tp,trackrampSEW,,,,,,wf,,,wy,,,ic,,,,Mrsssqqqq(2x1),,Mrsssqqqq(2x1) -,,,Tc,trackrampNSEW +a,Mg,,CS +b,Mh(1x1),S,CSa,,,,,,Mw,,,wm,,,wp +c,Mhs(1x1),m,CSaa,,,,,,,,,,,,,,,,,D +n,Mv,v,CSaaa,,Msu +,Mr(1x1),j,CSaaaa,,,,,,Mws,,,wu,,,ew +d,Mrq(1x1),A,CSd,,,Msk +,Mrqq(1x1),R,CSda +l,Mrqqq(1x1),N,CSdaa,,Msm,,,,we,,,wn,,,es,,,,,k +x,Mrqqqq(1x1),~h,CSdaaa +H,Mrs(1x1),~a,CSdaaaa,,,Msh +W,Mrsq(1x1),~c,CSdd,,,,,,wq,,,wr,,,el +G,Mrsqq(1x1),F,CSdda +B,Mrsqqq(1x1),o(1x1),CSddaa,,,,,,,,,,,,,,,,,ws +~,~b,Mrsqqqq(1x1),CSddaaa,,,,,,wM,,,wt,,,eg +~,f,Mrss(1x1),CSddaaaa +~,h,Mrssq(1x1),CSddd +~,r,Mrssqq(1x1),CSddda,,,,,,wo,,,wl,,,ea,,,,gx(1x2),gx(1x2) +~,s,Mrssqqq(1x1),CSdddaa +~,~s,Mrssqqqq(1x1),CSdddaaa,,,,,,,,,,,,,,gd(2x1),,gs(2x1),,ga(2x1) +~,t,Mrsss(1x1),CSdddaaaa,,,,,,wk,,,ww,,,ek,,gd(2x1),,gs(2x1),,ga(2x1) +gs(1x1),Mrsssq(1x1),,CSdddd,,,,,,,,,,,,,,,,gw(1x2),gw(1x2) +ga(1x1),Mrsssqq(1x1),,CSdddda +gd(1x1),Mrsssqqq(1x1),,CSddddaa,,,,,,wb,,,wz,,,en +gw(1x1),Mrsssqqqq(1x1),,CSddddaaa,,,,,,,,,,,,,,Mh(2x1),,Mh(2x1),,Mhs(1x2),Mhs(1x2) +gx(1x1),,,CSddddaaaa,,,,,,,,,,,,,,Mh(2x1),,Mh(2x1) +,,,Ts,,,,,,wc,,,wh,,,ib,,Mr(1x2),Mr(1x2),Mrs(2x1),,Mhs(1x2),Mhs(1x2) +y,,,Tw,,,,,,,,,,,,,,,,Mrs(2x1) +Y,,,Tl,,,,,,,,,,,,,,Mr(1x2),Mr(1x2),Mrsq(2x1),,Mrsq(2x1) +,,,Tp,,,,,,wf,,,wy,,,ic,,,,Mrsssqqqq(2x1),,Mrsssqqqq(2x1) +,,,Tc ,,,TS ,,,,,,,,,,wv,,,wd,,,wj,,,wS diff --git a/data/blueprints/library/test/ecosystem/in/buildings-construct.csv b/data/blueprints/library/test/ecosystem/in/buildings-construct.csv new file mode 100644 index 000000000..e7e1d12bb --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/buildings-construct.csv @@ -0,0 +1,21 @@ +#build label(construct) + + + + + + + + + + + + + +Cw +Cf +Cr +Cu +Cd +Cx +CF diff --git a/data/blueprints/library/test/ecosystem/in/transform-build.csv b/data/blueprints/library/test/ecosystem/in/transform-build.csv index fdc2e18ce..7907220e7 100644 --- a/data/blueprints/library/test/ecosystem/in/transform-build.csv +++ b/data/blueprints/library/test/ecosystem/in/transform-build.csv @@ -7,21 +7,8 @@ Mw,,,Msh,CSddddaaaa Mhs(1x2),,,Msm ,,Mws,,Msu Mh(2x1),,,Mws -#build label(outer) hidden() -trackN,trackS,trackE,,trackW,trackNS -trackNE,trackNW,trackSE,,trackSW -trackEW,trackNSE,trackNSW - -trackNEW,trackSEW -trackNSEW -#build label(inner) hidden() -trackrampN,trackrampNS,trackrampNE -trackrampNSE,trackrampNSW -trackrampNSEW #meta label(chunk) hidden() /big shift(1 -13) -/outer shift(7 -13) -/inner shift(1 -4) #meta label(build) /chunk /chunk transform(cw) diff --git a/data/blueprints/library/test/ecosystem/in/transform-construct.csv b/data/blueprints/library/test/ecosystem/in/transform-construct.csv new file mode 100644 index 000000000..95717afa8 --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/transform-construct.csv @@ -0,0 +1,23 @@ +#build label(outer) hidden() +trackN,trackS,trackE,,trackW,trackNS +trackNE,trackNW,trackSE,,trackSW +trackEW,trackNSE,trackNSW + +trackNEW,trackSEW +trackNSEW +#build label(inner) hidden() +trackrampN,trackrampNS,trackrampNE +trackrampNSE,trackrampNSW +trackrampNSEW +#meta label(chunk) hidden() +/outer shift(7 -13) +/inner shift(1 -4) +#meta label(construct) +/chunk +/chunk transform(cw) +/chunk transform(cw cw) +/chunk transform(ccw) +/chunk transform(fliph) +/chunk transform(flipv) +/chunk transform(cw flipv) +/chunk transform(ccw flipv) diff --git a/test/quickfort/ecosystem.lua b/test/quickfort/ecosystem.lua index d55bc2389..b59b8909d 100644 --- a/test/quickfort/ecosystem.lua +++ b/test/quickfort/ecosystem.lua @@ -327,6 +327,7 @@ function test.end_to_end() do_dig_phase(phases.dig, area, spec) if phases.smooth then do_dig_phase(phases.smooth, area, spec) end if phases.carve then do_dig_phase(phases.carve, area, spec) end + if phases.construct then do_phase(phases.construct, area, spec) end if phases.build then do_phase(phases.build, area, spec) end if phases.place then do_phase(phases.place, area, spec) end if phases.zone then do_phase(phases.zone, area, spec) end From d887bee0a4e4f1358df34beac488706a065e0c5f Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 12 Oct 2022 15:46:11 -0700 Subject: [PATCH 765/854] Removes lingering diffs from World module --- library/include/modules/World.h | 1 - library/modules/World.cpp | 6 ------ 2 files changed, 7 deletions(-) diff --git a/library/include/modules/World.h b/library/include/modules/World.h index 4cba97692..659786ff5 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -35,7 +35,6 @@ distribution. #include "Module.h" #include "modules/Persistence.h" #include -#include #include "DataDefs.h" diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 3f6ccb2c7..12b339e95 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -28,7 +28,6 @@ distribution. #include #include #include -#include #include using namespace std; @@ -52,11 +51,6 @@ using namespace std; #include "df/map_block.h" #include "df/block_square_event_world_constructionst.h" #include "df/viewscreen_legendsst.h" -#include "df/d_init.h" -#include "df/viewscreen_dwarfmodest.h" -#include "df/ui.h" -#include "VTableInterpose.h" -#include "PluginManager.h" using namespace DFHack; using namespace df::enums; From 80d993e7343a2b4fd6d714bedcdfd895811963cc Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 12 Oct 2022 16:01:54 -0700 Subject: [PATCH 766/854] split rooms phase from query and record names --- docs/plugins/blueprint.rst | 5 +- plugins/blueprint.cpp | 93 +++++++++++++++++++++++++++--------- plugins/lua/blueprint.lua | 1 + test/quickfort/ecosystem.lua | 1 + 4 files changed, 76 insertions(+), 24 deletions(-) diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst index bde5bb77e..262a3bd5b 100644 --- a/docs/plugins/blueprint.rst +++ b/docs/plugins/blueprint.rst @@ -77,7 +77,10 @@ phases; just separate them with a space. ``zone`` Generate quickfort ``#zone`` blueprints for designating zones. ``query`` - Generate quickfort ``#query`` blueprints for configuring rooms. + Generate quickfort ``#query`` blueprints for configuring stockpiles and + naming buildings. +``rooms`` + Generate quickfort ``#query`` blueprints for defining rooms. If no phases are specified, phases are autodetected. For example, a ``#place`` blueprint will be created only if there are stockpiles in the blueprint area. diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 3dd912d28..bb4d03d32 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -6,6 +6,7 @@ */ #include +#include #include #include @@ -92,6 +93,7 @@ struct blueprint_options { bool place = false; bool zone = false; bool query = false; + bool rooms = false; static struct_identity _identity; }; @@ -116,6 +118,7 @@ static const struct_field_info blueprint_options_fields[] = { { struct_field_info::PRIMITIVE, "place", offsetof(blueprint_options, place), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "zone", offsetof(blueprint_options, zone), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "query", offsetof(blueprint_options, query), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "rooms", offsetof(blueprint_options, rooms), &df::identity_traits::identity, 0, 0 }, { struct_field_info::END } }; struct_identity blueprint_options::_identity(sizeof(blueprint_options), &df::allocator_fn, NULL, "blueprint_options", NULL, blueprint_options_fields); @@ -134,11 +137,36 @@ DFhackCExport command_result plugin_shutdown(color_ostream &) { return CR_OK; } +struct blueprint_processor; struct tile_context { + blueprint_processor *processor; bool pretty = false; df::building* b = NULL; }; +typedef vector bp_row; // index is x coordinate +typedef map bp_area; // key is y coordinate +typedef map bp_volume; // key is z coordinate + +typedef const char * (get_tile_fn)(const df::coord &pos, + const tile_context &ctx); +typedef void (init_ctx_fn)(const df::coord &pos, tile_context &ctx); + +struct blueprint_processor { + bp_volume mapdata; + const string mode; + const string phase; + const bool force_create; + get_tile_fn * const get_tile; + init_ctx_fn * const init_ctx; + std::set seen; + blueprint_processor(const string &mode, const string &phase, + bool force_create, get_tile_fn *get_tile, + init_ctx_fn *init_ctx) + : mode(mode), phase(phase), force_create(force_create), + get_tile(get_tile), init_ctx(init_ctx) { } +}; + // global engravings cache, cleared when the string cache is cleared struct PosHash { size_t operator()(const df::coord &c) const { @@ -974,7 +1002,45 @@ static const char * get_tile_zone(const df::coord &pos, return add_expansion_syntax(zone, get_zone_keys(zone)); } -static const char * get_tile_query(const df::coord &, const tile_context &ctx) { +static string csv_sanitize(const string &str) { + static const std::regex pattern("\""); + static const string replacement("\"\""); + + return std::regex_replace(str, pattern, replacement); +} + +static const char * get_tile_query(const df::coord &pos, + const tile_context &ctx) { + string bld_name, zone_name; + auto & seen = ctx.processor->seen; + + if (ctx.b && !seen.count(ctx.b)) { + bld_name = ctx.b->name; + seen.emplace(ctx.b); + } + + vector civzones; + if (Buildings::findCivzonesAt(&civzones, pos)) { + auto civzone = civzones.back(); + if (!seen.count(civzone)) { + zone_name = civzone->name; + seen.emplace(civzone); + } + } + + if (!bld_name.size() && !zone_name.size()) + return NULL; + + std::ostringstream str; + if (bld_name.size()) + str << "{givename name=\"" << csv_sanitize(bld_name) << "\"}"; + if (zone_name.size()) + str << "{namezone name=\"" << csv_sanitize(zone_name) << "\"}"; + + return cache(str); +} + +static const char * get_tile_rooms(const df::coord &, const tile_context &ctx) { if (!ctx.b || !ctx.b->is_room) return NULL; return "r+"; @@ -1027,28 +1093,6 @@ static bool get_filename(string &fname, return true; } -typedef vector bp_row; // index is x coordinate -typedef map bp_area; // key is y coordinate -typedef map bp_volume; // key is z coordinate - -typedef const char * (get_tile_fn)(const df::coord &pos, - const tile_context &ctx); -typedef void (init_ctx_fn)(const df::coord &pos, tile_context &ctx); - -struct blueprint_processor { - bp_volume mapdata; - const string mode; - const string phase; - const bool force_create; - get_tile_fn * const get_tile; - init_ctx_fn * const init_ctx; - blueprint_processor(const string &mode, const string &phase, - bool force_create, get_tile_fn *get_tile, - init_ctx_fn *init_ctx) - : mode(mode), phase(phase), force_create(force_create), - get_tile(get_tile), init_ctx(init_ctx) { } -}; - static void write_minimal(ofstream &ofile, const blueprint_options &opts, const bp_volume &mapdata) { if (mapdata.begin() == mapdata.end()) @@ -1194,6 +1238,8 @@ static bool do_transform(color_ostream &out, add_processor(processors, opts, "zone", "zone", opts.zone, get_tile_zone); add_processor(processors, opts, "query", "query", opts.query, get_tile_query, ensure_building); + add_processor(processors, opts, "query", "rooms", opts.rooms, + get_tile_rooms, ensure_building); if (processors.empty()) { out.printerr("no phases requested! nothing to do!\n"); @@ -1212,6 +1258,7 @@ static bool do_transform(color_ostream &out, tile_context ctx; ctx.pretty = pretty; for (blueprint_processor &processor : processors) { + ctx.processor = &processor; if (processor.init_ctx) processor.init_ctx(pos, ctx); const char *tile_str = processor.get_tile(pos, ctx); diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua index 564962448..6fe869d3b 100644 --- a/plugins/lua/blueprint.lua +++ b/plugins/lua/blueprint.lua @@ -11,6 +11,7 @@ local valid_phase_list = { 'place', 'zone', 'query', + 'rooms', } valid_phases = utils.invert(valid_phase_list) diff --git a/test/quickfort/ecosystem.lua b/test/quickfort/ecosystem.lua index b59b8909d..27dddc5d5 100644 --- a/test/quickfort/ecosystem.lua +++ b/test/quickfort/ecosystem.lua @@ -332,6 +332,7 @@ function test.end_to_end() if phases.place then do_phase(phases.place, area, spec) end if phases.zone then do_phase(phases.zone, area, spec) end if phases.query then do_phase(phases.query, area, spec) end + if phases.rooms then do_phase(phases.rooms, area, spec) end -- run any extra commands, if defined by the blueprint spec if spec.extra_fn then From b03911945d26e63b0f576f121429b5f79624ca12 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 12 Oct 2022 16:03:03 -0700 Subject: [PATCH 767/854] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 37f3a1dd1..70e144f3f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -40,6 +40,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - `blueprint`: new ``--smooth`` option for recording all smoothed floors and walls instead of just the ones that require smoothing for later carving - `blueprint`: record built constructions in blueprints +- `blueprint`: record stockpile/building/zone names in blueprints - `ls`: indent tag listings and wrap them in the right column for better readability - `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output. - `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down From f411ba76ba9c161df755cbc03c726c19520fee32 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 12 Oct 2022 16:33:06 -0700 Subject: [PATCH 768/854] record accurate room sizes in blueprints --- docs/changelog.txt | 1 + plugins/blueprint.cpp | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 70e144f3f..71d843764 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,6 +41,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `blueprint`: new ``--smooth`` option for recording all smoothed floors and walls instead of just the ones that require smoothing for later carving - `blueprint`: record built constructions in blueprints - `blueprint`: record stockpile/building/zone names in blueprints +- `blueprint`: record room sizes in blueprints - `ls`: indent tag listings and wrap them in the right column for better readability - `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output. - `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index bb4d03d32..2f42e2798 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -1043,7 +1043,29 @@ static const char * get_tile_query(const df::coord &pos, static const char * get_tile_rooms(const df::coord &, const tile_context &ctx) { if (!ctx.b || !ctx.b->is_room) return NULL; - return "r+"; + + // get the maximum distance from the center of the building + df::building_extents &room = ctx.b->room; + int32_t x1 = room.x; + int32_t x2 = room.x + room.width - 1; + int32_t y1 = room.y; + int32_t y2 = room.y + room.height - 1; + + int32_t dimx = std::max(ctx.b->centerx - x1, x2 - ctx.b->centerx); + int32_t dimy = std::max(ctx.b->centery - y1, y2 - ctx.b->centery); + int32_t max_dim = std::max(dimx, dimy); + + switch (max_dim) { + case 0: return "r---&"; + case 1: return "r--&"; + case 2: return "r-&"; + case 3: return "r&"; + case 4: return "r+&"; + } + + std::ostringstream str; + str << "r{+ " << (max_dim - 3) << "}&"; + return cache(str); } static bool create_output_dir(color_ostream &out, From c52138b16874e59665dd75261132ca27167d0194 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 12 Oct 2022 17:42:36 -0700 Subject: [PATCH 769/854] generate meta blueprints --- docs/changelog.txt | 1 + docs/plugins/blueprint.rst | 10 ++++-- plugins/blueprint.cpp | 70 ++++++++++++++++++++++++++++++++++-- plugins/lua/blueprint.lua | 17 +++++++++ test/plugins/blueprint.lua | 6 ++++ test/quickfort/ecosystem.lua | 2 +- 6 files changed, 100 insertions(+), 6 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 71d843764..c2173d5e1 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `blueprint`: record built constructions in blueprints - `blueprint`: record stockpile/building/zone names in blueprints - `blueprint`: record room sizes in blueprints +- `blueprint`: generate meta blueprints to reduce the number of blueprints you have to apply - `ls`: indent tag listings and wrap them in the right column for better readability - `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output. - `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst index 262a3bd5b..3a5e5a0c5 100644 --- a/docs/plugins/blueprint.rst +++ b/docs/plugins/blueprint.rst @@ -99,8 +99,14 @@ Options Select the output format of the generated files. See the `Output formats`_ section below for options. If not specified, the output format defaults to "minimal", which will produce a small, fast ``.csv`` file. -``-h``, ``--help`` - Show command help text. +``--nometa`` + `Meta blueprints ` let you apply all blueprints that can be + replayed at the same time (without unpausing the game) with a single + command. This usually reduces the number of `quickfort` commands you need to + run to rebuild your fort from about 6 to 2 or 3. If you would rather just + have the low-level blueprints, this flag will prevent meta blueprints from + being generated and any low-level blueprints from being + `hidden ` from the ``quickfort list`` command. ``-s``, ``--playback-start ,,`` Specify the column and row offsets (relative to the upper-left corner of the blueprint, which is ``1,1``) where the player should put the cursor when the diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 2f42e2798..b08f4d074 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -14,6 +14,7 @@ #include "DataDefs.h" #include "DataFuncs.h" #include "DataIdentity.h" +#include "Debug.h" #include "LuaTools.h" #include "PluginManager.h" #include "TileTypes.h" @@ -47,6 +48,10 @@ using namespace DFHack; DFHACK_PLUGIN("blueprint"); REQUIRE_GLOBAL(world); +namespace DFHack { + DBG_DECLARE(blueprint,log); +} + struct blueprint_options { // whether to display help bool help = false; @@ -59,6 +64,9 @@ struct blueprint_options { // for it. string format; + // whether to skip generating meta blueprints + bool nometa = false; + // offset and comment to write in the quickfort start() modeline marker // if not set, coordinates are set to 0 and the comment will be empty df::coord2d playback_start = df::coord2d(0, 0); @@ -101,6 +109,7 @@ static const struct_field_info blueprint_options_fields[] = { { struct_field_info::PRIMITIVE, "help", offsetof(blueprint_options, help), &df::identity_traits::identity, 0, 0 }, { struct_field_info::SUBSTRUCT, "start", offsetof(blueprint_options, start), &df::coord::_identity, 0, 0 }, { struct_field_info::PRIMITIVE, "format", offsetof(blueprint_options, format), df::identity_traits::get(), 0, 0 }, + { struct_field_info::PRIMITIVE, "nometa", offsetof(blueprint_options, nometa), &df::identity_traits::identity, 0, 0 }, { struct_field_info::SUBSTRUCT, "playback_start", offsetof(blueprint_options, playback_start), &df::coord2d::_identity, 0, 0 }, { struct_field_info::PRIMITIVE, "playback_start_comment", offsetof(blueprint_options, playback_start_comment), df::identity_traits::get(), 0, 0 }, { struct_field_info::PRIMITIVE, "split_strategy", offsetof(blueprint_options, split_strategy), df::identity_traits::get(), 0, 0 }, @@ -1115,6 +1124,32 @@ static bool get_filename(string &fname, return true; } +// returns true if we could interface with lua and could verify that the given +// phase is a meta phase +static bool is_meta_phase(color_ostream &out, + blueprint_options opts, // copy because we can't const + const string &phase) { + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 3) || + !Lua::PushModulePublic( + out, L, "plugins.blueprint", "is_meta_phase")) { + out.printerr("Failed to load blueprint Lua code\n"); + return false; + } + + Lua::Push(L, &opts); + Lua::Push(L, phase); + + if (!Lua::SafeCall(out, L, 2, 1)) { + out.printerr("Failed Lua call to is_meta_phase\n"); + return false; + } + + return lua_toboolean(L, -1); +} + static void write_minimal(ofstream &ofile, const blueprint_options &opts, const bp_volume &mapdata) { if (mapdata.begin() == mapdata.end()) @@ -1171,8 +1206,8 @@ static void write_pretty(ofstream &ofile, const blueprint_options &opts, } } -static string get_modeline(const blueprint_options &opts, const string &mode, - const string &phase) { +static string get_modeline(color_ostream &out, const blueprint_options &opts, + const string &mode, const string &phase) { std::ostringstream modeline; modeline << "#" << mode << " label(" << phase << ")"; if (opts.playback_start.x > 0) { @@ -1183,6 +1218,8 @@ static string get_modeline(const blueprint_options &opts, const string &mode, } modeline << ")"; } + if (is_meta_phase(out, opts, phase)) + modeline << " hidden()"; return modeline.str(); } @@ -1199,7 +1236,7 @@ static bool write_blueprint(color_ostream &out, output_files[fname] = new ofstream(fname, ofstream::trunc); ofstream &ofile = *output_files[fname]; - ofile << get_modeline(opts, processor.mode, processor.phase) << endl; + ofile << get_modeline(out, opts, processor.mode, processor.phase) << endl; if (pretty) write_pretty(ofile, opts, processor.mapdata); @@ -1209,6 +1246,27 @@ static bool write_blueprint(color_ostream &out, return true; } +static void write_meta_blueprint(color_ostream &out, + std::map &output_files, + const blueprint_options &opts, + const std::vector & meta_phases) { + string fname; + get_filename(fname, out, opts, meta_phases.front()); + ofstream &ofile = *output_files[fname]; + + ofile << "#meta label("; + for (string phase : meta_phases) { + ofile << phase; + if (phase != meta_phases.back()) + ofile << "_"; + } + ofile << ")" << endl; + + for (string phase : meta_phases) { + ofile << "/" << phase << endl; + } +} + static void ensure_building(const df::coord &pos, tile_context &ctx) { if (ctx.b) return; @@ -1301,12 +1359,18 @@ static bool do_transform(color_ostream &out, } std::map output_files; + std::vector meta_phases; for (blueprint_processor &processor : processors) { if (processor.mapdata.empty() && !processor.force_create) continue; + if (is_meta_phase(out, opts, processor.phase)) + meta_phases.push_back(processor.phase); if (!write_blueprint(out, output_files, opts, processor, pretty)) break; } + if (meta_phases.size()) { + write_meta_blueprint(out, output_files, opts, meta_phases); + } for (auto &it : output_files) { filenames.push_back(it.first); diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua index 6fe869d3b..e3b36d9fe 100644 --- a/plugins/lua/blueprint.lua +++ b/plugins/lua/blueprint.lua @@ -15,6 +15,14 @@ local valid_phase_list = { } valid_phases = utils.invert(valid_phase_list) +local meta_phase_list = { + 'build', + 'place', + 'zone', + 'query', +} +meta_phases = utils.invert(meta_phase_list) + local valid_formats_list = { 'minimal', 'pretty', @@ -123,6 +131,7 @@ local function process_args(opts, args) {'f', 'format', hasArg=true, handler=function(optarg) parse_format(opts, optarg) end}, {'h', 'help', handler=function() opts.help = true end}, + {nil, 'nometa', handler=function() opts.nometa = true end}, {'s', 'playback-start', hasArg=true, handler=function(optarg) parse_start(opts, optarg) end}, {nil, 'smooth', handler=function() opts.smooth = true end}, @@ -185,6 +194,11 @@ function parse_commandline(opts, ...) parse_positionals(opts, positionals, depth and 4 or 3) end +function is_meta_phase(opts, phase) + -- this is called directly by cpp so ensure we return a boolean, not nil + return not opts.nometa and meta_phases[phase] or false +end + -- returns the name of the output file for the given context function get_filename(opts, phase) local fullname = 'blueprints/' .. opts.name @@ -197,6 +211,9 @@ function get_filename(opts, phase) fullname = fullname .. basename end if opts.split_strategy == 'phase' then + if is_meta_phase(opts, phase) then + phase = 'meta' + end return ('%s-%s.csv'):format(fullname, phase) end -- no splitting diff --git a/test/plugins/blueprint.lua b/test/plugins/blueprint.lua index a19d519b8..73ec33eda 100644 --- a/test/plugins/blueprint.lua +++ b/test/plugins/blueprint.lua @@ -20,6 +20,12 @@ function test.parse_gui_commandline() b.parse_gui_commandline(opts, {'-h'}) expect.table_eq({help=true, format='minimal', split_strategy='none'}, opts) + opts = {} + b.parse_gui_commandline(opts, {'--nometa'}) + expect.table_eq({auto_phase=true, format='minimal', split_strategy='none', + name='blueprint', nometa=true}, + opts) + opts = {} mock.patch(dfhack.maps, 'isValidTilePos', mock.func(true), function() diff --git a/test/quickfort/ecosystem.lua b/test/quickfort/ecosystem.lua index 27dddc5d5..05f531e2e 100644 --- a/test/quickfort/ecosystem.lua +++ b/test/quickfort/ecosystem.lua @@ -244,7 +244,7 @@ end local function run_blueprint(basename, spec, pos) local args = {tostring(spec.width), tostring(spec.height), tostring(-spec.depth), output_dir..basename, - get_cursor_arg(pos), '-tphase'} + get_cursor_arg(pos), '-tphase', '--nometa'} local playback_start_arg = get_playback_start_arg(spec.start) if playback_start_arg then table.insert(args, playback_start_arg) From b4986aad97f73c6f961874d6543001af1f9e08cf Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 12 Oct 2022 17:49:37 -0700 Subject: [PATCH 770/854] create meta bp only if it will reduce the bp count --- plugins/blueprint.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index b08f4d074..361aaa1ca 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -1285,7 +1285,7 @@ static void add_processor(vector &processors, static bool do_transform(color_ostream &out, const df::coord &start, const df::coord &end, - const blueprint_options &opts, + blueprint_options &opts, vector &filenames) { // empty map instances to pass to emplace() below static const bp_area EMPTY_AREA; @@ -1358,17 +1358,24 @@ static bool do_transform(color_ostream &out, } } - std::map output_files; std::vector meta_phases; for (blueprint_processor &processor : processors) { if (processor.mapdata.empty() && !processor.force_create) continue; if (is_meta_phase(out, opts, processor.phase)) meta_phases.push_back(processor.phase); + } + if (meta_phases.size() <= 1) + opts.nometa = true; + + std::map output_files; + for (blueprint_processor &processor : processors) { + if (processor.mapdata.empty() && !processor.force_create) + continue; if (!write_blueprint(out, output_files, opts, processor, pretty)) break; } - if (meta_phases.size()) { + if (!opts.nometa) { write_meta_blueprint(out, output_files, opts, meta_phases); } From e2953a840e3f4cdf89015d167d1f2d2c4bef2d15 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 13 Oct 2022 07:33:58 +0000 Subject: [PATCH 771/854] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 937cf27f9..c297cb6ea 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 937cf27f9b41301be6df0fe1d75d77b6f089f688 +Subproject commit c297cb6ea702213bfc22e2cdef47e580cff84f2b From 2c9ce5f31612f9d5fc1c52088254cdbf96c671fb Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 13 Oct 2022 11:52:39 -0700 Subject: [PATCH 772/854] Includes pause.h in spectate's cmake as to.. "changes to the header file will trigger recompliation" --- plugins/spectate/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/spectate/CMakeLists.txt b/plugins/spectate/CMakeLists.txt index 5ba1ed9cf..15e5cb172 100644 --- a/plugins/spectate/CMakeLists.txt +++ b/plugins/spectate/CMakeLists.txt @@ -1,8 +1,11 @@ project(spectate) +set_source_files_properties(pause.h PROPERTIES HEADER_FILE_ONLY TRUE) + SET(SOURCES spectate.cpp + pause.h pause.cpp) dfhack_plugin(${PROJECT_NAME} ${SOURCES} LINK_LIBRARIES lua) From 880e9b34ef9e58704eaa5a86c53a02f16078d503 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 13 Oct 2022 11:52:56 -0700 Subject: [PATCH 773/854] Removes disable usage --- docs/plugins/spectate.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/plugins/spectate.rst b/docs/plugins/spectate.rst index eea85d3da..51c1eaf45 100644 --- a/docs/plugins/spectate.rst +++ b/docs/plugins/spectate.rst @@ -11,7 +11,6 @@ Usage :: enable spectate - disable spectate spectate