Merge branch 'develop' into spectate

develop
Josh Cooper 2022-10-12 14:40:56 -07:00
commit 8d8ef023fd
76 changed files with 1037 additions and 643 deletions

@ -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

@ -0,0 +1,22 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
build:
os: ubuntu-20.04
tools:
python: "3"
submodules:
include: all
sphinx:
configuration: conf.py
formats: all
python:
install:
- requirements: .readthedocs.requirements.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}")

@ -58,9 +58,6 @@ def doc_dir(dirname, files, prefix):
def doc_all_dirs():
"""Collect the commands and paths to include in our docs."""
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/builtins'):
tools.extend(doc_dir(root, files, os.path.relpath(root, 'docs/builtins')))
for root, _, files in os.walk('docs/plugins'):
@ -69,76 +66,30 @@ def doc_all_dirs():
tools.extend(doc_dir(root, files, os.path.relpath(root, 'scripts/docs')))
return tuple(tools)
DOC_ALL_DIRS = doc_all_dirs()
def get_tags():
groups = {}
group_re = re.compile(r'"([^"]+)"')
tag_re = re.compile(r'- `tag/([^`]+)`: (.*)')
with open('docs/Tags.rst') as f:
lines = f.readlines()
for line in lines:
line = line.strip()
m = re.match(group_re, line)
if m:
group = m.group(1)
groups[group] = []
continue
m = re.match(tag_re, line)
if m:
tag = m.group(1)
desc = m.group(2)
groups[group].append((tag, desc))
return groups
def generate_tag_indices():
os.makedirs('docs/tags', mode=0o755, exist_ok=True)
tag_groups = get_tags()
for tag_group in tag_groups:
with write_file_if_changed(('docs/tags/by{group}.rst').format(group=tag_group)) as topidx:
for tag_tuple in tag_groups[tag_group]:
tag = tag_tuple[0]
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))
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():
"""
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.
the original documentation.
"""
for k in DOC_ALL_DIRS:
header = ':orphan:\n'
for k in doc_all_dirs():
label = ('.. _{name}:\n\n').format(name=k[0])
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
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)
# Actually call the docs generator and run test
write_tool_docs()
generate_tag_indices()
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = '1.8'
needs_sphinx = '3.4.3'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
@ -315,11 +266,9 @@ html_sidebars = {
]
}
# If false, no module index is generated.
html_domain_indices = False
# If false, no genindex.html is generated.
html_use_index = True
# generate domain indices but not the (unused) genindex
html_use_index = False
html_domain_indices = True
# don't link to rst sources in the generated pages
html_show_sourcelink = False

@ -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,7 @@ 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
prioritize -a --haul-labor=Food,Body StoreItemInStockpile
prioritize -a --reaction-name=TAN_A_HIDE CustomReaction
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
@ -78,7 +73,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

@ -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."

@ -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/bugfix` script to run on `repeat`.
a `bugfix-tag-index` script to run on `repeat`.
.. _onMapLoad.init:

@ -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`_

@ -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 <license>`, contributions are welcome.
`various open-source licenses <license>`, 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 <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.

@ -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
@ -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.
@ -3172,7 +3179,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 +3264,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 +3274,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 +3346,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 +3668,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 +3904,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``
@ -4025,6 +4032,52 @@ 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.
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.
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
-----------
@ -4045,17 +4098,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_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.
The text itself is represented as a complex structure, and passed
to the object via the ``text`` argument of the constructor, or via
@ -4148,7 +4190,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
------------------
@ -4276,7 +4319,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.
@ -4437,7 +4479,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 +4503,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 +4514,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 +4548,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 +4782,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 +4838,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 +4897,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 +4940,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 +5019,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 +5136,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:

@ -63,7 +63,7 @@ are not built by default, but can be built by setting the ``BUILD_DEVEL``
Scripts
~~~~~~~
Several `development tools <tag/dev>` can be useful for memory research.
Several `development tools <dev-tag-index>` can be useful for memory research.
These include (but are not limited to):
- `devel/dump-offsets`

@ -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.

@ -1,3 +0,0 @@
:orphan:
Please continue to the new `support` page.

@ -13,36 +13,36 @@ for the tag assignment spreadsheet.
"when" tags
-----------
- `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/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/embark`: Tools that are useful while on the fort embark screen or while creating an adventurer.
- `tag/fort`: Tools that are useful while in fort mode.
- `tag/legends`: Tools that are useful while in legends mode.
- `adventure <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 <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 <embark-tag-index>`: Tools that are useful while on the fort embark screen or while creating an adventurer.
- `fort <fort-tag-index>`: Tools that are useful while in fort mode.
- `legends <legends-tag-index>`: Tools that are useful while in legends mode.
"why" tags
----------
- `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/auto`: Tools that run in the background and automatically manage routine, toilsome aspects of your fortress.
- `tag/bugfix`: Tools that fix specific bugs, either permanently or on-demand.
- `tag/design`: Tools that help you design your fort.
- `tag/dev`: Tools that are useful when developing scripts or mods.
- `tag/fps`: Tools that help you manage FPS drop.
- `tag/gameplay`: Tools that introduce new gameplay elements.
- `tag/inspection`: Tools that let you view information that is otherwise difficult to find.
- `tag/productivity`: Tools that help you do things that you could do manually, but using the tool is better and faster.
- `armok <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 <auto-tag-index>`: Tools that run in the background and automatically manage routine, toilsome aspects of your fortress.
- `bugfix <bugfix-tag-index>`: Tools that fix specific bugs, either permanently or on-demand.
- `design <design-tag-index>`: Tools that help you design your fort.
- `dev <dev-tag-index>`: Tools that are useful when developing scripts or mods.
- `fps <fps-tag-index>`: Tools that help you manage FPS drop.
- `gameplay <gameplay-tag-index>`: Tools that introduce new gameplay elements.
- `inspection <inspection-tag-index>`: Tools that let you view information that is otherwise difficult to find.
- `productivity <productivity-tag-index>`: Tools that help you do things that you could do manually, but using the tool is better and faster.
"what" tags
-----------
- `tag/animals`: Tools that interact with animals.
- `tag/buildings`: Tools that interact with buildings and furniture.
- `tag/graphics`: Tools that interact with game graphics.
- `tag/interface`: Tools that interact with or extend the DF user interface.
- `tag/items`: Tools that interact with in-game items.
- `tag/jobs`: Tools that interact with jobs.
- `tag/labors`: Tools that deal with labor assignment.
- `tag/map`: Tools that interact with the game map.
- `tag/military`: Tools that interact with the military.
- `tag/plants`: Tools that interact with trees, shrubs, and crops.
- `tag/stockpiles`: Tools that interact wtih stockpiles.
- `tag/units`: Tools that interact with units.
- `tag/workorders`: Tools that interact with workorders.
- `animals <animals-tag-index>`: Tools that interact with animals.
- `buildings <buildings-tag-index>`: Tools that interact with buildings and furniture.
- `graphics <graphics-tag-index>`: Tools that interact with game graphics.
- `interface <interface-tag-index>`: Tools that interact with or extend the DF user interface.
- `items <items-tag-index>`: Tools that interact with in-game items.
- `jobs <jobs-tag-index>`: Tools that interact with jobs.
- `labors <labors-tag-index>`: Tools that deal with labor assignment.
- `map <map-tag-index>`: Tools that interact with the game map.
- `military <military-tag-index>`: Tools that interact with the military.
- `plants <plants-tag-index>`: Tools that interact with trees, shrubs, and crops.
- `stockpiles <stockpiles-tag-index>`: Tools that interact with stockpiles.
- `units <units-tag-index>`: Tools that interact with units.
- `workorders <workorders-tag-index>`: Tools that interact with workorders.

@ -30,9 +30,11 @@ guildhalls and temples that you have agreed to.
Finding the tool you need
-------------------------
DFHack tools are tagged with categories 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 in one flat list, please refer to the `index <genindex>`.
DFHack tools are tagged with categories to make them easier to find. These
categories are listed in the next few sections. Note that a tool can belong to
more than one category. If you already know what you're looking for, try the
`search` or Ctrl-F on this page. If you'd like to see the full list of tools in
one flat list, please refer to the `annotated index <all-tag-index>`.
DFHack tools by game mode
-------------------------
@ -48,3 +50,14 @@ DFHack tools by what they affect
--------------------------------
.. include:: tags/bywhat.rst
All DFHack tools alphabetically
-------------------------------
.. toctree::
:glob:
:maxdepth: 1
:titlesonly:
tools/*
tools/*/*

@ -40,3 +40,5 @@ Options
Don't print out the tags associated with each command.
``--dev``
Include commands intended for developers and modders.
``--exclude <string>[,<string>...]``
Exclude commands that match any of the given strings.

@ -33,12 +33,31 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
# Future
## New Plugins
## Fixes
## 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
- 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
## 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
## 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.
## New Tweaks
- `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 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`.
@ -49,6 +68,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 <quickfort-blueprint-guide>` 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.
@ -86,6 +106,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`` 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.

@ -129,7 +129,7 @@ provides two libraries for this, ``repeat-util`` and `eventful <eventful-api>`.
``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
-----------------
@ -348,6 +348,9 @@ timer::
The structure of a full mod
---------------------------
For reference, `Tachy Guns <https://www.github.com/wolfboyft/tachy-guns>`__ is a
full mod that conforms to this guide.
Create a folder for mod projects somewhere outside your Dwarf Fortress
installation directory (e.g. ``/path/to/mymods/``) and use your mod IDs as the
names for the mod folders within it. In the example below, we'll use a mod ID of
@ -425,13 +428,18 @@ Ok, you're all set up! Now, let's take a look at an example
moduleA.onLoad()
moduleB.onLoad()
-- register your callbacks
repeatUtil.scheduleEvery(modId .. ' every tick', 1, 'ticks',
moduleA.every1Tick)
-- multiple functions in the same repeat callback
repeatUtil.scheduleEvery(modId .. ' every tick', 1, 'ticks', function()
moduleA.every1Tick()
moduleB.every1Tick()
end)
-- one function per repeat callback (you can put them in the
-- above format if you prefer)
repeatUtil.scheduleEvery(modId .. ' 100 frames', 1, 'frames',
moduleD.every100Frames)
-- multiple functions in the same callback
-- multiple functions in the same eventful callback
eventful.onReactionComplete[modId] = function(reaction,
reaction_product, unit, input_items, input_reagents,
output_items)
@ -442,7 +450,7 @@ Ok, you're all set up! Now, let's take a look at an example
unit, input_items, input_reagents, output_items)
end
-- one function per callback (you can put them in the
-- one function per eventful callback (you can put them in the
-- above format if you prefer)
eventful.onProjItemCheckMovement[modId] = moduleD.onProjItemCheckMovement
eventful.onProjUnitCheckMovement[modId] = moduleD.onProjUnitCheckMovement

@ -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.

@ -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``.

@ -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`).
@ -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 <data/blueprints/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 <quickfort-dig-priorities>`.
Use dig priorities to control ramp creation.
We can `ensure <https://docs.google.com/spreadsheets/d/1IBy6_pGEe6WSBCLukDz_5I-4vi_mpHuJJyOp2j6SJlY/edit#gid=962076234>`__
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).

@ -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

@ -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.

@ -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,

@ -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

@ -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,16 +39,16 @@ 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
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 <minimum> 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 a skill are deprioritized for
other skills. The labor is then added to the best <minimum> 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

@ -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.

@ -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

@ -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 <strategy>``
Split blueprints into multiple files. See the `Splitting output into
multiple files`_ section below for details. If not specified, defaults to

@ -34,7 +34,7 @@ Usage
``burrow remove-units target-burrow <burrow> [<burrow> ...]``
Remove units in source burrows from the target.
``burrow set-tiles target-burrow <burrow> [<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 <burrow> [<burrow> ...]``
Add tiles from the source burrows to the target.
``burrow remove-tiles target-burrow <burrow> [<burrow> ...]``

@ -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.

@ -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.

@ -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 <lua>` or `:rb <rb>`
portions of the command already filled in.

@ -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::

@ -42,13 +42,13 @@ Usage
-----
``debugfilter category [<plugin regex>] [<category regex>]``
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 [<id>]``
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 [<level>] [<plugin regex>] [<category regex>]``
Create a new debug filter to set category verbosity levels. This filter
@ -61,7 +61,7 @@ Usage
``debugfilter disable <id> [<id> ...]``
Disable a space separated list of filters but keep it in the filter list.
``debugfilter enable <id> [<id> ...]``
Enable a space sperate list of filters.
Enable a space separated list of filters.
``debugfilter header [enable] | [disable] [<element> ...]``
Control which header metadata is shown along with each log message. Run it
without parameters to see the list of configurable elements. Include an

@ -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 [<designation>] [-p<number>]``
Designate all vein tiles of the selected type. See the `digtype`_ section
below for options.
``digtype [<designation>] [-p<number>] [-z]``
Designate all vein tiles of the same type as the selected tile. See the
`digtype`_ section below for options.
``digexp [<pattern>] [<filter>] [-p<number>]``
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
------

@ -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,

@ -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
-----

@ -14,7 +14,7 @@ Usage
embark-tools enable|disable all
embark-tools enable|disable <tool> [<tool> ...]
Avaliable tools are:
Available tools are:
``anywhere``
Allows embarking anywhere (including sites, mountain-only biomes, and

@ -18,7 +18,7 @@ Usage
fix-unit-occupancy interval <num_ticks>
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 <num_ticks>``
Set how often the plugin will check for and fix issues when it is enabled.
The default is 1200 ticks, or 1 game day.

@ -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

@ -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 <labor> <value>``
Set the priority value for labor <labor> to <value>.
``labormanager max <labor> <value>``
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 <labor> none``
Unrestrict the number of dwarves that can be assigned to a labor.
``labormanager max <labor> disable``

@ -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).

@ -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)

@ -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:

@ -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:

@ -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.

@ -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 <hour>|cycle``

@ -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
-----

@ -7,19 +7,19 @@ reveal
:summary: Reveals the map.
:tags: adventure fort armok inspection map
.. dfhack-tool:: unreveal
.. dfhack-command:: unreveal
:summary: Hides previously hidden tiles again.
.. dfhack-tool:: revforget
.. dfhack-command:: revforget
:summary: Discard records about what was visible before revealing the map.
.. dfhack-tool:: revtoggle
.. dfhack-command:: revtoggle
:summary: Switch between reveal and unreveal.
.. dfhack-tool:: revflood
.. dfhack-command:: revflood
:summary: Hide everything, then reveal tiles with a path to the cursor.
.. dfhack-tool:: nopause
.. dfhack-command:: nopause
:summary: Disable pausing.
This reveals all z-layers in fort mode. It also works in adventure mode, but any

@ -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.

@ -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 <type> <target>``
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 <type>``

@ -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.

@ -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.

@ -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.

@ -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

@ -122,7 +122,7 @@ The constraint spec consists of 4 parts, separated with ``/`` characters::
ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,<quality>]
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 <Material_token>`.
The subsequent parts are optional:

@ -74,8 +74,8 @@ Filters
:all: Process all units.
:count <n>: Process only up to n units.
:unassigned: Not assigned to zone, chain or built cage.
:minage <years>: Minimum age. Must be followed by number.
:maxage <years>: Maximum age. Must be followed by number.
:minage <years>: Minimum age. Must be followed by a number.
:maxage <years>: 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,

@ -3,15 +3,21 @@
# 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 collections import defaultdict
import logging
import os
from typing import List, Optional, Type
import re
from typing import Dict, Iterable, List, Optional, Tuple, Type
import docutils.nodes as nodes
from docutils.nodes import Node
import docutils.parsers.rst.directives as rst_directives
import sphinx
import sphinx.addnodes as addnodes
import sphinx.directives
from sphinx.domains import Domain, Index, IndexEntry
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import process_index_entry
import dfhack.util
@ -46,7 +52,6 @@ def make_summary(builder: sphinx.builders.Builder, summary: str) -> nodes.paragr
para += nodes.inline(text=summary)
return para
_KEYBINDS = {}
_KEYBINDS_RENDERED = set() # commands whose keybindings have been rendered
@ -105,21 +110,40 @@ 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
optional_arguments = 1
def get_name_or_docname(self):
if self.arguments:
return self.arguments[0]
else:
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]
return self.get_tool_name_from_docname()
def add_index_entries(self, name) -> None:
docname = self.env.docname
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)
for tag in tags:
self.env.domaindata[tag]['objects'].append(indexdata)
@staticmethod
def wrap_box(*children: List[nodes.Node]) -> nodes.Admonition:
return nodes.topic('', *children, classes=['dfhack-tool-summary'])
@ -145,13 +169,15 @@ 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',
'refdomain': 'std',
'reftarget': 'tag/' + tag,
'refexplicit': False,
'reftarget': tag + '-tag-index',
'refexplicit': True,
'refwarn': True,
}),
nodes.inline(text=' | '),
@ -160,6 +186,7 @@ class DFHackToolDirective(DFHackToolDirectiveBase):
ret_nodes = [tag_paragraph]
if 'no-command' in self.options:
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
@ -177,6 +204,7 @@ class DFHackCommandDirective(DFHackToolDirectiveBase):
def render_content(self) -> List[nodes.Node]:
command = self.get_name_or_docname()
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', '')),
@ -184,16 +212,114 @@ 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'"([^"]+)"')
tag_re = re.compile(r'- `([^ ]+) <[^>]+>`: (.*)')
with open(os.path.join(dfhack.util.DOCS_ROOT, 'Tags.rst')) as f:
lines = f.readlines()
for line in lines:
line = line.strip()
m = re.match(group_re, line)
if m:
group = m.group(1)
groups[group] = []
continue
m = re.match(tag_re, line)
if m:
tag = m.group(1)
desc = m.group(2)
groups[group].append((tag, desc))
return groups
def tag_domain_get_objects(self):
for obj in self.data['objects']:
yield(obj)
def tag_domain_merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
seen = set()
objs = self.data['objects']
for obj in objs:
seen.add(obj[0])
for obj in otherdata['objects']:
if obj[0] not in seen:
objs.append(obj)
objs.sort()
def tag_index_generate(self, docnames: Optional[Iterable[str]] = None) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]:
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, extra, '', descr))
return (sorted(content.items()), False)
def register_index(app, tag, title):
domain_class = type(tag+'Domain', (Domain, ), {
'name': tag,
'label': 'Container domain for tag: ' + tag,
'initial_data': {'objects': []},
'merge_domaindata': tag_domain_merge_domaindata,
'get_objects': tag_domain_get_objects,
})
index_class = type(tag+'Index', (Index, ), {
'name': 'tag-index',
'localname': title,
'shortname': tag,
'generate': tag_index_generate,
})
app.add_domain(domain_class)
app.add_index_to_domain(tag, index_class)
def init_tag_indices(app):
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:
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))
topidx.write((' {desc}\n').format(desc=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<h4>%s</h4>' % (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)
app.add_domain(TagRepoDomain)
register_index(app, 'all', 'Index of DFHack tools')
init_tag_indices(app)
# TODO: re-enable once detection is corrected
# app.connect('build-finished', lambda *_: check_missing_keybinds())

@ -622,12 +622,18 @@ void ls_helper(color_ostream &con, const vector<string> &params) {
vector<string> 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<string> &params) {
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<string> &params) {
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");
}
}

@ -359,6 +359,149 @@ function EditField:onInput(keys)
return self.modal
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{
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.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
-- 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 = (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
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 < -scrollbar.bar_height then
scrollbar.on_scroll('up_large')
elseif delta < 0 then
scrollbar.on_scroll('up_small')
elseif delta > scrollbar.bar_height 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)
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)
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
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_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'
else
self.is_dragging = y - self.bar_offset
return true
end
self.scroll_spec = scroll_spec
self.on_scroll(scroll_spec)
-- reset continuous scroll state
self.is_first_click = true
self.last_scroll_ms = dfhack.getTickCount()
return true
end
-----------
-- Label --
-----------
@ -546,12 +689,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)
@ -560,6 +706,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
@ -569,87 +721,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
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(
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()
@ -659,6 +732,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]
@ -682,44 +759,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)
@ -740,24 +792,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()
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)
@ -787,7 +843,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
@ -953,6 +1009,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 +1078,21 @@ function List:postComputeFrame(body)
self:moveCursor(0)
end
local function update_list_scrollbar(list)
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)
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 +1115,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.ceil(self.page_size / 2)
elseif scroll_spec == 'up_large' then
v = -math.ceil(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 +1217,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

@ -14,6 +14,8 @@
local _ENV = mkmodule('helpdb')
local argparse = require('argparse')
local MAX_STALE_MS = 60000
-- paths
@ -120,7 +122,7 @@ No help available.
]]
local function make_default_entry(entry_name, help_source, kwargs)
local default_long_help = DEFAULT_HELP_TEMPLATE:format(
entry_name, ('*'):rep(#entry_name))
entry_name, ('='):rep(#entry_name))
return {
help_source=help_source,
short_help='No help available.',
@ -275,7 +277,7 @@ local function make_script_entry(old_entry, entry_name, kwargs)
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),
end_marker=(is_rb and SCRIPT_DOC_END_RUBY or SCRIPT_DOC_END),
first_line_is_short_help=(is_rb and '#' or '%-%-')})
return entry
end
@ -321,6 +323,8 @@ local function scan_builtins(old_db)
HELP_SOURCES.RENDERED or HELP_SOURCES.STUB,
{entry_types=entry_types})
end
-- easter egg: replace underline for 'die' help with tombstones
textdb.die.long_help = textdb.die.long_help:gsub('=', string.char(239))
end
-- scan for enableable plugins and plugin-provided commands and add their help
@ -394,7 +398,7 @@ local function initialize_tags()
desc = desc .. ' ' .. line
tag_index[tag].description = desc
else
_,_,tag,desc = line:find('^%* (%w+): (.+)')
_,_,tag,desc = line:find('^%* (%w+)[^:]*: (.+)')
if not tag then goto continue end
tag_index[tag] = {description=desc}
in_desc = true
@ -586,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
@ -628,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)
@ -642,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,
@ -656,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
@ -724,13 +753,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.')
@ -739,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()

@ -308,9 +308,8 @@ df::building *Buildings::findAtTile(df::coord pos)
static unordered_map<int32_t, df::coord> corner1;
static unordered_map<int32_t, df::coord> 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))
{

@ -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)

@ -1 +1 @@
Subproject commit f5fab13fb652dd953e9d59e8deca032b26131208
Subproject commit f2b59b8d5036cca90a88245d0d7c8b2a713f60ee

@ -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<int32_t>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "depth", offsetof(blueprint_options, depth), &df::identity_traits<int32_t>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "name", offsetof(blueprint_options, name), df::identity_traits<string>::get(), 0, 0 },
{ struct_field_info::PRIMITIVE, "smooth", offsetof(blueprint_options, smooth), &df::identity_traits<bool>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "engrave", offsetof(blueprint_options, engrave), &df::identity_traits<bool>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "auto_phase", offsetof(blueprint_options, auto_phase), &df::identity_traits<bool>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "dig", offsetof(blueprint_options, dig), &df::identity_traits<bool>::identity, 0, 0 },
@ -219,7 +222,7 @@ static const char * get_tile_smooth_minimal(const df::coord &pos,
return NULL;
}
static const char * get_tile_smooth(const df::coord &pos,
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)
@ -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<blueprint_processor> 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,
@ -1187,21 +1218,9 @@ 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,
static command_result do_blueprint(color_ostream &out,
const vector<string> &parameters,
vector<string> &files) {
CoreSuspender suspend;
@ -1221,13 +1240,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 +1254,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 +1283,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 +1302,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 +1312,13 @@ static int run(lua_State *L) {
command_result blueprint(color_ostream &out, vector<string> &parameters) {
vector<string> 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 {

@ -35,6 +35,7 @@ command_result digtype (color_ostream &out, vector <string> & 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 <PluginCommand> &commands)
{
@ -1417,16 +1418,18 @@ command_result digtype (color_ostream &out, vector <string> & 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 <string> & 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);

@ -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 <width> <height> [<depth>] [<name> [<phases>]] [<options>]
blueprint gui [<name> [<phases>]] [<options>]
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',
@ -154,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},
})

@ -70,7 +70,6 @@
#include "df/viewscreen_topicmeetingst.h"
#include "df/viewscreen_topicmeeting_takerequestsst.h"
#include "df/viewscreen_tradeagreementst.h"
#include "df/viewscreen_tradegoodsst.h"
#include "df/viewscreen_tradelistst.h"
#include "df/viewscreen_treasurelistst.h"
#include "df/viewscreen_unitlist_page.h"
@ -211,7 +210,6 @@ IMPLEMENT_HOOKS(topicmeeting_fill_land_holder_positions)
IMPLEMENT_HOOKS(topicmeeting)
IMPLEMENT_HOOKS(topicmeeting_takerequests)
IMPLEMENT_HOOKS(tradeagreement)
IMPLEMENT_HOOKS(tradegoods)
IMPLEMENT_HOOKS(tradelist)
IMPLEMENT_HOOKS(treasurelist)
IMPLEMENT_HOOKS(unitlist)
@ -310,7 +308,6 @@ DFhackCExport command_result plugin_enable(color_ostream &, bool enable) {
INTERPOSE_HOOKS_FAILED(topicmeeting) ||
INTERPOSE_HOOKS_FAILED(topicmeeting_takerequests) ||
INTERPOSE_HOOKS_FAILED(tradeagreement) ||
INTERPOSE_HOOKS_FAILED(tradegoods) ||
INTERPOSE_HOOKS_FAILED(tradelist) ||
INTERPOSE_HOOKS_FAILED(treasurelist) ||
INTERPOSE_HOOKS_FAILED(unitlist) ||

@ -1 +1 @@
Subproject commit 484988bb3d64f7aed9442af3b226faa77d8fd00d
Subproject commit 937cf27f9b41301be6df0fe1d75d77b6f089f688

@ -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

@ -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

@ -36,37 +36,36 @@ local mock_script_db = {
inscript_docs=true,
inscript_short_only=true,
nodocs_script=true,
dev_script=true,
}
local files = {
['hack/docs/docs/Tags.txt']=[[
* fort: Tools that are useful while in fort mode.
* armok: Tools that give you complete control over
an aspect of the game or provide access to
information that the game intentionally keeps
hidden.
* armok: Tools that give you complete control over an aspect of the game or provide access to information that the game intentionally keeps hidden.
* map: Tools that interact with the game map.
* units: Tools that interact with units.
* dev: Dev tools.
* nomembers: Nothing is tagged with this.
]],
['hack/docs/docs/tools/hascommands.txt']=[[
hascommands
***********
===========
**Tags:** fort | armok | units
Tags: fort | armok | units
Documented a plugin that
has commands.
Documented a plugin that has commands.
**Command:** "boxbinders"
Command: "boxbinders"
Documented boxbinders.
**Command:** "bindboxers"
Command: "bindboxers"
Documented bindboxers.
@ -74,12 +73,11 @@ Documented full help.
]],
['hack/docs/docs/tools/samename.txt']=[[
samename
********
========
**Tags:** fort | armok
| units
Tags: fort | armok | units
**Command:** "samename"
Command: "samename"
Documented samename.
@ -87,10 +85,9 @@ Documented full help.
]],
['hack/docs/docs/tools/nocommand.txt']=[[
nocommand
*********
=========
**Tags:** fort | armok |
units
Tags: fort | armok | units
Documented nocommand.
@ -98,11 +95,11 @@ Documented full help.
]],
['hack/docs/docs/tools/basic.txt']=[[
basic
*****
=====
**Tags:** map
Tags: map
**Command:** "basic"
Command: "basic"
Documented basic.
@ -110,15 +107,29 @@ Documented full help.
]],
['hack/docs/docs/tools/subdir/scriptname.txt']=[[
subdir/scriptname
*****************
=================
**Tags:** map
Tags: map
**Command:** "subdir/scriptname"
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
@ -126,9 +137,9 @@ Documented full help.
basic
=====
**Tags:** map
Tags: map
**Command:** "basic"
Command: "basic"
in-file basic.
@ -142,9 +153,9 @@ script contents
subdir/scriptname
=================
**Tags:** map
Tags: map
**Command:** "subdir/scriptname"
Command: "subdir/scriptname"
in-file scriptname.
@ -158,9 +169,9 @@ script contents
inscript_docs
=============
**Tags:** map | badtag
Tags: map | badtag
**Command:** "inscript_docs"
Command: "inscript_docs"
in-file inscript_docs.
@ -182,9 +193,9 @@ script contents
basic
=====
**Tags:** map
Tags: map
**Command:** "basic"
Command: "basic"
in-file basic (other).
@ -198,9 +209,9 @@ script contents
subdir/scriptname
=================
**Tags:** map
Tags: map
**Command:** "subdir/scriptname"
Command: "subdir/scriptname"
in-file scriptname (other).
@ -214,14 +225,17 @@ script contents
inscript_docs
=============
**Tags:** map
Tags: map
**Command:** "inscript_docs"
Command: "inscript_docs"
in-file inscript_docs (other).
Documented full help.
]====]
script contents
]],
['other/scriptpath/dev_script.lua']=[[
script contents
]],
}
@ -400,11 +414,11 @@ end
function test.get_entry_long_help()
local expected = [[
basic
*****
=====
**Tags:** map
Tags: map
**Command:**
Command:
"basic"
Documented
@ -432,34 +446,34 @@ full help.
-- plugins/commands that have no doc files get the default template
expect.eq([[ls
**
==
No help available.
]], h.get_entry_long_help('ls'))
expect.eq([[nodocs_hascommands
******************
==================
No help available.
]], h.get_entry_long_help('nodocs_hascommands'))
expect.eq([[nodocs_hascommands
******************
==================
No help available.
]], h.get_entry_long_help('nodoc_command'))
expect.eq([[Nodocs samename.
This command has the same name as its host plugin but no rst docs.]], h.get_entry_long_help('nodocs_samename'))
expect.eq([[nodocs_nocommand
****************
================
No help available.
]], h.get_entry_long_help('nodocs_nocommand'))
expect.eq([[nodocs_script
*************
=============
No help available.
]], h.get_entry_long_help('nodocs_script'))
expect.eq([[inscript_short_only
*******************
===================
No help available.
]], h.get_entry_long_help('inscript_short_only'))
@ -468,9 +482,9 @@ No help available.
expect.eq([[inscript_docs
=============
**Tags:** map | badtag
Tags: map | badtag
**Command:** "inscript_docs"
Command: "inscript_docs"
in-file inscript_docs.
@ -501,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
@ -540,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',
@ -561,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',
@ -604,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
@ -676,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

@ -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',