Merge remote-tracking branch 'DFHack/develop' into develop

# Conflicts:
#	library/xml
develop
Rose 2023-01-04 15:02:33 -08:00
commit 0d18dd508a
32 changed files with 606 additions and 496 deletions

@ -20,7 +20,7 @@ keybinding add Ctrl-Shift-C hotkeys
keybinding add Ctrl-Shift-K gui/cp437-table
# an in-game init file editor
keybinding add Alt-S@title|dwarfmode/Default|dungeonmode gui/settings-manager
#keybinding add Alt-S@title|dwarfmode/Default|dungeonmode gui/settings-manager
######################
@ -28,142 +28,142 @@ keybinding add Alt-S@title|dwarfmode/Default|dungeonmode gui/settings-manager
######################
# quicksave, only in main dwarfmode screen and menu page
keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave
#keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave
# toggle the display of water level as 1-7 tiles
keybinding add Ctrl-W@dwarfmode|dungeonmode twaterlvl
# designate the whole vein for digging
keybinding add Ctrl-V@dwarfmode digv
keybinding add Ctrl-Shift-V@dwarfmode "digv x"
#keybinding add Ctrl-V@dwarfmode digv
#keybinding add Ctrl-Shift-V@dwarfmode "digv x"
# clean the selected tile of blood etc
keybinding add Ctrl-C spotclean
#keybinding add Ctrl-C spotclean
# destroy the selected item
keybinding add Ctrl-K@dwarfmode autodump-destroy-item
#keybinding add Ctrl-K@dwarfmode autodump-destroy-item
# destroy items designated for dump in the selected tile
keybinding add Ctrl-Shift-K@dwarfmode autodump-destroy-here
#keybinding add Ctrl-Shift-K@dwarfmode autodump-destroy-here
# apply blueprints to the map (Alt-F for compatibility with LNP Quickfort)
keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort
keybinding add Alt-F@dwarfmode gui/quickfort
#keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort
#keybinding add Alt-F@dwarfmode gui/quickfort
# show information collected by dwarfmonitor
keybinding add Alt-M@dwarfmode/Default "dwarfmonitor prefs"
keybinding add Ctrl-F@dwarfmode/Default "dwarfmonitor stats"
#keybinding add Alt-M@dwarfmode/Default "dwarfmonitor prefs"
#keybinding add Ctrl-F@dwarfmode/Default "dwarfmonitor stats"
# set the zone or cage under the cursor as the default
keybinding add Alt-Shift-I@dwarfmode/Zones "zone set"
#keybinding add Alt-Shift-I@dwarfmode/Zones "zone set"
# Stocks plugin
keybinding add Ctrl-Shift-Z@dwarfmode/Default "stocks show"
#keybinding add Ctrl-Shift-Z@dwarfmode/Default "stocks show"
# open an overview window summarising some stocks (dfstatus)
keybinding add Ctrl-Shift-I@dwarfmode/Default|dfhack/lua/dfstatus gui/dfstatus
#keybinding add Ctrl-Shift-I@dwarfmode/Default|dfhack/lua/dfstatus gui/dfstatus
# change quantity of manager orders
keybinding add Alt-Q@jobmanagement/Main gui/manager-quantity
#keybinding add Alt-Q@jobmanagement/Main gui/manager-quantity
# re-check manager orders
keybinding add Alt-R@jobmanagement/Main workorder-recheck
#keybinding add Alt-R@jobmanagement/Main workorder-recheck
# set workorder item details (on workorder details screen press D again)
keybinding add D@workquota_details gui/workorder-details
#keybinding add D@workquota_details gui/workorder-details
# view combat reports for the selected unit/corpse/spatter
keybinding add Ctrl-Shift-R@dwarfmode|unit|unitlist|joblist|dungeon_monsterstatus|layer_unit_relationship|item|workshop_profile|layer_noblelist|locations|pets|layer_overall_health|textviewer|reportlist|announcelist|layer_military|layer_unit_health|customize_unit|buildinglist|workshop_profile view-unit-reports
#keybinding add Ctrl-Shift-R@dwarfmode|unit|unitlist|joblist|dungeon_monsterstatus|layer_unit_relationship|item|workshop_profile|layer_noblelist|locations|pets|layer_overall_health|textviewer|reportlist|announcelist|layer_military|layer_unit_health|customize_unit|buildinglist|workshop_profile view-unit-reports
# view extra unit information
keybinding add Alt-I@dwarfmode/ViewUnits|unitlist gui/unit-info-viewer
#keybinding add Alt-I@dwarfmode/ViewUnits|unitlist gui/unit-info-viewer
# boost priority of jobs related to the selected entity
keybinding add Alt-N@dwarfmode|job|joblist|unit|unitlist|joblist|dungeon_monsterstatus|layer_unit_relationship|item|layer_noblelist|locations|pets|layer_overall_health|textviewer|reportlist|announcelist|layer_military|layer_unit_health|customize_unit|buildinglist|textviewer|item|layer_assigntrade|tradegoods|store|assign_display_item|treasurelist do-job-now
#keybinding add Alt-N@dwarfmode|job|joblist|unit|unitlist|joblist|dungeon_monsterstatus|layer_unit_relationship|item|layer_noblelist|locations|pets|layer_overall_health|textviewer|reportlist|announcelist|layer_military|layer_unit_health|customize_unit|buildinglist|textviewer|item|layer_assigntrade|tradegoods|store|assign_display_item|treasurelist do-job-now
# export a Dwarf's preferences screen in BBCode to post to a forum
keybinding add Ctrl-Shift-F@textviewer forum-dwarves
#keybinding add Ctrl-Shift-F@textviewer forum-dwarves
# q->stockpile - copy & paste stockpiles
keybinding add Alt-P@dwarfmode/QueryBuilding/Some/Stockpile copystock
#keybinding add Alt-P@dwarfmode/QueryBuilding/Some/Stockpile copystock
# q->stockpile - load and save stockpile settings out of game
keybinding add Alt-L@dwarfmode/QueryBuilding/Some/Stockpile "gui/stockpiles -load"
keybinding add Alt-S@dwarfmode/QueryBuilding/Some/Stockpile "gui/stockpiles -save"
#keybinding add Alt-L@dwarfmode/QueryBuilding/Some/Stockpile "gui/stockpiles -load"
#keybinding add Alt-S@dwarfmode/QueryBuilding/Some/Stockpile "gui/stockpiles -save"
# q->workshop - duplicate the selected job
keybinding add Ctrl-D job-duplicate
#keybinding add Ctrl-D job-duplicate
# materials: q->workshop; b->select items
keybinding add Shift-A "job-material ALUNITE"
keybinding add Shift-M "job-material MICROCLINE"
keybinding add Shift-D "job-material DACITE"
keybinding add Shift-R "job-material RHYOLITE"
keybinding add Shift-I "job-material CINNABAR"
keybinding add Shift-B "job-material COBALTITE"
keybinding add Shift-O "job-material OBSIDIAN"
keybinding add Shift-T "job-material ORTHOCLASE"
keybinding add Shift-G "job-material GLASS_GREEN"
#keybinding add Shift-A "job-material ALUNITE"
#keybinding add Shift-M "job-material MICROCLINE"
#keybinding add Shift-D "job-material DACITE"
#keybinding add Shift-R "job-material RHYOLITE"
#keybinding add Shift-I "job-material CINNABAR"
#keybinding add Shift-B "job-material COBALTITE"
#keybinding add Shift-O "job-material OBSIDIAN"
#keybinding add Shift-T "job-material ORTHOCLASE"
#keybinding add Shift-G "job-material GLASS_GREEN"
# sort units and items in the on-screen list
keybinding add Alt-Shift-N "sort-units name" "sort-items description"
keybinding add Alt-Shift-R "sort-units arrival"
keybinding add Alt-Shift-T "sort-units profession" "sort-items type material"
keybinding add Alt-Shift-Q "sort-units squad_position" "sort-items quality"
#keybinding add Alt-Shift-N "sort-units name" "sort-items description"
#keybinding add Alt-Shift-R "sort-units arrival"
#keybinding add Alt-Shift-T "sort-units profession" "sort-items type material"
#keybinding add Alt-Shift-Q "sort-units squad_position" "sort-items quality"
# browse linked mechanisms
keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms
#keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms
# browse rooms of same owner
keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list
#keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list
# interface for the liquids plugin - spawn water/magma/obsidian
keybinding add Alt-L@dwarfmode/LookAround gui/liquids
#keybinding add Alt-L@dwarfmode/LookAround gui/liquids
# machine power sensitive pressure plate construction
keybinding add Ctrl-Shift-M@dwarfmode/Build/Position/Trap gui/power-meter
#keybinding add Ctrl-Shift-M@dwarfmode/Build/Position/Trap gui/power-meter
# siege engine control
keybinding add Alt-A@dwarfmode/QueryBuilding/Some/SiegeEngine gui/siege-engine
#keybinding add Alt-A@dwarfmode/QueryBuilding/Some/SiegeEngine gui/siege-engine
# military weapon auto-select
keybinding add Ctrl-W@layer_military/Equip/Customize/View gui/choose-weapons
#keybinding add Ctrl-W@layer_military/Equip/Customize/View gui/choose-weapons
# military copy uniform
keybinding add Ctrl-C@layer_military/Uniforms gui/clone-uniform
#keybinding add Ctrl-C@layer_military/Uniforms gui/clone-uniform
# minecart Guide path
keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path
#keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path
# workshop job details
keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job
#keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job
# workflow front-end
keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow
keybinding add Alt-W@overallstatus "gui/workflow status"
#keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow
#keybinding add Alt-W@overallstatus "gui/workflow status"
# equivalent to the one above when gui/extended-status is enabled
keybinding add Alt-W@dfhack/lua/status_overlay "gui/workflow status"
#keybinding add Alt-W@dfhack/lua/status_overlay "gui/workflow status"
# autobutcher front-end
keybinding add Shift-B@pet/List/Unit gui/autobutcher
#keybinding add Shift-B@pet/List/Unit gui/autobutcher
# view pathable tiles from active cursor
keybinding add Alt-Shift-P@dwarfmode/LookAround gui/pathable
#keybinding add Alt-Shift-P@dwarfmode/LookAround gui/pathable
# gui/rename script - rename units and buildings
keybinding add Ctrl-Shift-N@dwarfmode|unit|unitlist|joblist|dungeon_monsterstatus|layer_unit_relationship|item|workshop_profile|layer_noblelist|locations|pets|layer_overall_health|textviewer|reportlist|announcelist|layer_military|layer_unit_health|customize_unit|buildinglist gui/rename
keybinding add Ctrl-Shift-T@dwarfmode|unit|unitlist|joblist|dungeon_monsterstatus|layer_unit_relationship|item|workshop_profile|layer_noblelist|locations|pets|layer_overall_health|textviewer|reportlist|announcelist|layer_military|layer_unit_health|customize_unit "gui/rename unit-profession"
#keybinding add Ctrl-Shift-N@dwarfmode|unit|unitlist|joblist|dungeon_monsterstatus|layer_unit_relationship|item|workshop_profile|layer_noblelist|locations|pets|layer_overall_health|textviewer|reportlist|announcelist|layer_military|layer_unit_health|customize_unit|buildinglist gui/rename
#keybinding add Ctrl-Shift-T@dwarfmode|unit|unitlist|joblist|dungeon_monsterstatus|layer_unit_relationship|item|workshop_profile|layer_noblelist|locations|pets|layer_overall_health|textviewer|reportlist|announcelist|layer_military|layer_unit_health|customize_unit "gui/rename unit-profession"
#####################
# adv mode bindings #
#####################
keybinding add Ctrl-A@dungeonmode/ConversationSpeak adv-rumors
keybinding add Ctrl-B@dungeonmode adv-bodyswap
keybinding add Ctrl-Shift-B@dungeonmode "adv-bodyswap force"
keybinding add Shift-O@dungeonmode gui/companion-order
keybinding add Ctrl-T@dungeonmode gui/advfort
#keybinding add Ctrl-A@dungeonmode/ConversationSpeak adv-rumors
#keybinding add Ctrl-B@dungeonmode adv-bodyswap
#keybinding add Ctrl-Shift-B@dungeonmode "adv-bodyswap force"
#keybinding add Shift-O@dungeonmode gui/companion-order
#keybinding add Ctrl-T@dungeonmode gui/advfort
#########################
@ -171,4 +171,4 @@ keybinding add Ctrl-T@dungeonmode gui/advfort
#########################
# export all information, or just the detailed maps (doesn't handle site maps)
keybinding add Ctrl-A@legends "exportlegends all"
#keybinding add Ctrl-A@legends "exportlegends all"

@ -1,13 +1,4 @@
{
"dwarfmonitor.date": {
"enabled": true
},
"dwarfmonitor.misery": {
"enabled": true
},
"dwarfmonitor.weather": {
"enabled": true
},
"hotkeys.menu": {
"enabled": true
},

@ -0,0 +1,7 @@
You can put scripts you write or download in this folder and DFHack will find
them.
If a script in this directory has the same name as a default DFHack script, the
script in this directory will take precedence.
Everything you add to this folder will be kept safe when you upgrade DFHack.

@ -21,10 +21,11 @@ DFHack commands can be implemented in any of three ways:
same version of DFHack. They are less flexible than scripts,
but used for complex or ongoing tasks because they run faster.
:scripts: are Ruby or Lua scripts stored in ``hack/scripts/``.
Because they don't need to be compiled, scripts are
more flexible about versions, and easier to distribute.
Most third-party DFHack addons are scripts.
:scripts: are Ruby or Lua scripts stored in ``hack/scripts/`` or other
directories in the `script-paths`. Because they don't need to
be compiled, scripts are more flexible about versions, and
they are easier to distribute. Most third-party DFHack addons
are scripts.
All tools distributed with DFHack are documented `here <tools>`.
@ -37,6 +38,8 @@ DFHack commands can be executed in a number of ways:
#. Pressing a key combination set up with `keybinding`
#. From one of several `init-files`, automatically
#. Using `script` to run a batch of commands from a file
#. From an in-game command launcher interface like `gui/launcher`, the
`hotkeys` overlay widget, or `gui/quickcmd`.
The DFHack console
------------------
@ -144,7 +147,7 @@ save-specific init files in the save folders.
DFHack looks for init files in three places each time they could be run:
#. The :file:`dfhack-config/init` subdirectory in the main DF directory
#. :file:`data/save/{world}/raw`, where ``world`` is the current save, and
#. :file:`data/save/{world}/raw`, where ``{world}`` is the current save, and
#. :file:`data/save/{world}/raw/objects`
For each of those directories, all matching init files will be executed in
@ -171,7 +174,7 @@ dfhack\*.init
On startup, DFHack looks for files of the form ``dfhack*.init`` (where ``*`` is
a placeholder for any string, including the empty string).
These files are best used for keybindings and enabling persistent plugins
These files are best used for keybindings and enabling persistent tools
which do not require a world to be loaded.
@ -230,9 +233,10 @@ Script paths are folders that DFHack searches to find a script when a command is
run. By default, the following folders are searched, in order (relative to the
root DF folder):
1. :file:`data/save/{<region folder>}/raw/scripts` (only if a save is loaded)
2. :file:`raw/scripts`
3. :file:`hack/scripts`
#. :file:`dfhack-config/scripts`
#. :file:`data/save/{<region folder>}/raw/scripts` (only if a save is loaded)
#. :file:`raw/scripts`
#. :file:`hack/scripts`
For example, if ``teleport`` is run, these folders are searched in order for
``teleport.lua`` or ``teleport.rb``, and the first matching file is run.

@ -38,17 +38,21 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Fixes
## Misc Improvements
- Scrollable widgets now react to mouse wheel events when the mouse is over the widget
- the ``dfhack-config/scripts/`` folder is now searched for scripts by default
## Documentation
- `overlay-dev-guide`: added troubleshooting tips and common development workflows
- added DFHack architecture diagrams to the dev intro
## API
- ``Gui::getDwarfmodeDims``: now only returns map viewport dimensions; menu dimensions are obsolete
## Lua
- ``gui.View``: ``visible`` and ``active`` can now be functions that return a boolean
- ``widgets.Panel``: new attributes to control window dragging and resizing with mouse or keyboard
- ``widgets.Window``: Panel subclass with attributes preset for top-level windows
- `overlay`: ``OverlayWidget`` now inherits from ``Panel`` instead of ``Widget`` to get all the frame and mouse integration goodies
## Internals

@ -1011,7 +1011,7 @@ Fortress mode
* ``dfhack.gui.getDwarfmodeViewDims()``
Returns dimensions of the main fortress mode screen. See ``getPanelLayout()``
Returns dimensions of the displayed map viewport. See ``getPanelLayout()``
in the ``gui.dwarfmode`` module for a more Lua-friendly version.
* ``dfhack.gui.resetDwarfmodeView([pause])``
@ -1121,7 +1121,8 @@ 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 overridden by plugins.
the closest rendered ground z-level below. Defaults to 0, unless overridden by
plugins.
Job module
----------
@ -2161,7 +2162,14 @@ Functions:
* ``dfhack.screen.getMousePos()``
Returns *x,y* of the tile the mouse is over.
Returns *x,y* of the UI interface tile the mouse is over, with the upper left
corner being ``0,0``. To get the map tile coordinate that the mouse is over,
see ``dfhack.gui.getMousePos()``.
* ``dfhack.screen.getMousePixels()``
Returns *x,y* of the screen coordinates the mouse is over in pixels, with the
upper left corner being ``0,0``.
* ``dfhack.screen.inGraphicsMode()``
@ -2355,7 +2363,14 @@ Supported callbacks and fields are:
where the above painting functions work correctly.
If omitted, the screen is cleared; otherwise it should do that itself.
In order to make a see-through dialog, call ``self._native.parent:render()``.
In order to make a dialog where portions of the parent viewscreen are still
visible in the background, call ``screen:renderParent()``.
If artifacts are left on the parent even after this function is called, such
as when the window is dragged or is resized, any code can set
``gui.Screen.request_full_screen_refresh`` to ``true``. Then when
``screen.renderParent()`` is next called, it will do a full flush of the
graphics and clear the screen of artifacts.
* ``function screen:onIdle()``
@ -2614,8 +2629,8 @@ and are only documented here for completeness:
Registers ``path`` as a `script path <script-paths>`.
If ``search_before`` is passed and ``true``, the path will be searched before
the default paths (e.g. ``raw/scripts``, ``hack/scripts``); otherwise, it will
be searched after.
the default paths (e.g. ``dfhack-config/scripts``, ``hack/scripts``); otherwise,
it will be searched after.
Returns ``true`` if successful or ``false`` otherwise (e.g. if the path does
not exist or has already been registered).
@ -3632,11 +3647,6 @@ considered stable.
Misc
----
* ``USE_GRAPHICS``
Contains the value of ``dfhack.screen.inGraphicsMode()``, which cannot be
changed without restarting the game and thus is constant during the session.
* ``CLEAR_PEN``
The black pen used to clear the screen.
@ -4144,6 +4154,8 @@ Base of all the widgets. Inherits from View and has the following attributes:
The pen to fill the outer frame with. Defaults to no fill.
.. _panel:
Panel class
-----------
@ -4306,7 +4318,6 @@ Attributes:
If it returns false, the character is ignored.
:on_change: Change notification callback; used as ``on_change(new_text,old_text)``.
:on_submit: Enter key callback; if set the field will handle the key and call ``on_submit(text)``.
:on_submit2: Shift-Enter key callback; if set the field will handle the key and call ``on_submit2(text)``.
:key: If specified, the field is disabled until this key is pressed. Must be given as a string.
:key_sep: If specified, will be used to customize how the activation key is
displayed. See ``token.key_sep`` in the ``Label`` documentation below.
@ -4336,21 +4347,36 @@ You can click where you want the cursor to move or you can use any of the
following keyboard hotkeys:
- Left/Right arrow: move the cursor one character to the left or right.
- Ctrl-Left/Right arrow: move the cursor one word to the left or right.
- Alt-Left/Right arrow: move the cursor to the beginning/end of the text.
- Ctrl-B/Ctrl-F: move the cursor one word back or forward.
- Ctrl-A/Ctrl-E: move the cursor to the beginning/end of the text.
The ``EditField`` class also provides the following functions:
* ``editfield:setCursor([cursor_pos])``
Sets the text insert cursor to the specified position. If ``cursor_pos`` is
not specified or is past the end of the current text string, the cursor will
be set to the end of the current input (that is, ``#editfield.text + 1``).
* ``editfield:setText(text[, cursor_pos])``
Sets the input text string and, optionally, the cursor position. If the
cursor position is not specified, it sets it to the end of the string.
* ``editfield:insert(text)``
Inserts the given text at the current cursor position.
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.
widget (like a `Label class`_ or a `List class`_). They are styled like scrollbars
used in vanilla DF.
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. If the scrollbar is clicked,
the callback will be called with one of the following string parameters: "up_large",
"down_large", "up_small", or "down_small". If the scrollbar is dragged, the callback will
@ -4377,6 +4403,10 @@ 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.
If the mouse wheel is scrolled while the mouse is over the Scrollbar widget's
parent view, then the parent is scrolled accordingly. Holding :kbd:`Shift`
while scrolling will result in faster movement.
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
@ -4621,8 +4651,8 @@ It has the following attributes:
with an empty list.
:on_submit: Enter key or mouse click callback; if specified, the list reacts to the
key/click and calls the callback as ``on_submit(index,choice)``.
:on_submit2: Shift-Enter key or shift-mouse click callback; if specified, the list
reacts to the key/click and calls it as ``on_submit2(index,choice)``.
:on_submit2: Shift-click callback; if specified, the list reacts to the click and
calls the callback as ``on_submit2(index,choice)``.
:row_height: Height of every row in text lines.
:icon_width: If not *nil*, the specified number of character columns
are reserved to the left of the list item for the icons.
@ -5455,11 +5485,11 @@ Scripts
:local:
Any files with the ``.lua`` extension placed into the :file:`hack/scripts` folder
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:
(or any other folder in your `script-paths`) 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:
* :file:`hack/scripts/add-thought.lua` is invoked as ``add-thought``
* :file:`dfhack-config/scripts/startup.lua` is invoked as ``startup``
* :file:`hack/scripts/gui/teleport.lua` is invoked as ``gui/teleport``
.. note::
@ -5476,12 +5506,6 @@ the extension omitted. For example:
a mod developer would want to run a script from the console, it should
not be placed in this folder)
Scripts can also be placed in other folders - by default, these include
:file:`raw/scripts` and :file:`data/save/{region}/raw/scripts`, but additional
folders can be added (for example, a copy of the
:source-scripts:`scripts repository <>` for local development). See
`script-paths` for more information on how to configure this behavior.
Scripts are read from disk when run for the first time, or if they have changed
since the last time they were run.
@ -5509,7 +5533,7 @@ General script API
* ``dfhack.run_script(name[,args...])``
Run a Lua script in :file:`hack/scripts/`, as if it were started from the
Run a Lua script in your `script-paths`, as if it were started from the
DFHack command-line. The ``name`` argument should be the name of the script
without its extension, as it would be used on the command line.
@ -5551,8 +5575,8 @@ Importing scripts
Loads a Lua script and returns its environment (i.e. a table of all global
functions and variables). This is similar to the built-in ``require()``, but
searches all script paths for the first matching ``name.lua`` file instead
of searching the Lua library paths (like ``hack/lua``).
searches all `script-paths` for the first matching ``name.lua`` file instead
of searching the Lua library paths (like ``hack/lua/``).
Most scripts can be made to support ``reqscript()`` without significant
changes (in contrast, ``require()`` requires the use of ``mkmodule()`` and

@ -38,7 +38,7 @@ Overlay widget API
------------------
Overlay widgets are Lua classes that inherit from ``overlay.OverlayWidget``
(which itself inherits from `widgets.Widget <widget>`). The regular
(which itself inherits from `widgets.Panel <panel>`). The regular
``onInput(keys)``, ``onRenderFrame(dc, frame_rect)``, and ``onRenderBody(dc)``
functions work as normal, and they are called when the viewscreen that the
widget is associated with does its usual input and render processing. The widget
@ -125,6 +125,10 @@ The ``overlay.OverlayWidget`` superclass defines the following class attributes:
not annoy the player. Set to 0 to be called at the maximum rate. Be aware
that running more often than you really need to will impact game FPS,
especially if your widget can run while the game is unpaused.
- ``always_enabled`` (default: ``false``)
Set this to ``true`` if you don't want to let the user disable the widget.
This is useful for widgets that are controlled purely through their
triggers. See `gui/pathable` for an example.
Registering a widget with the overlay framework
***********************************************

@ -60,9 +60,10 @@ For scripts with the same name, the `order of precedence <script-paths>` will
be:
1. ``own-scripts/``
2. ``data/save/*/raw/scripts/``
3. ``raw/scripts/``
4. ``hack/scripts/``
2. ``dfhack-config/scripts/``
3. ``data/save/*/raw/scripts/``
4. ``raw/scripts/``
5. ``hack/scripts/``
The structure of the game
-------------------------

@ -12,7 +12,7 @@ watch list. Units will be ignored if they are:
* Untamed
* Nicknamed (for custom protection; you can use the `rename` ``unit`` tool
individually, or `zone` ``nick`` for groups)
* Caged, if and only if the cage is defined as a room (to protect zoos)
* Caged, if and only if the cage is in a zone (to protect zoos)
* Trained for war or hunting
Creatures who will not reproduce (because they're not interested in the

@ -7,4 +7,4 @@ pathable
:no-command:
This plugin provides a Lua API, but no direct commands. See `pathable-api` for
details.
details and `gui/pathable` for the user interface.

@ -487,6 +487,7 @@ void Core::getScriptPaths(std::vector<std::string> *dest)
string df_path = this->p->getPath();
for (auto it = script_paths[0].begin(); it != script_paths[0].end(); ++it)
dest->push_back(*it);
dest->push_back(df_path + "/dfhack-config/scripts");
if (df::global::world && isWorldLoaded()) {
string save = World::ReadWorldFolder();
if (save.size())
@ -2266,6 +2267,7 @@ bool Core::ncurses_wgetch(int in, int & out)
}
if(in >= KEY_F(1) && in <= KEY_F(8))
{
/* TODO: understand how this changes for v50
int idx = in - KEY_F(1);
// FIXME: copypasta, push into a method!
if(df::global::ui && df::global::gview)
@ -2284,6 +2286,7 @@ bool Core::ncurses_wgetch(int in, int & out)
return true;
}
}
*/
}
out = in;
return true;
@ -2451,6 +2454,7 @@ bool Core::SelectHotkey(int sym, int modifiers)
int idx = sym - SDL::K_F1;
if(idx >= 0 && idx < 8)
{
/* TODO: understand how this changes for v50
if (modifiers & 1)
idx += 8;
@ -2460,6 +2464,7 @@ bool Core::SelectHotkey(int sym, int modifiers)
{
cmd = df::global::ui->main.hotkeys[idx].name;
}
*/
}
}
}

@ -1436,17 +1436,8 @@ static int gui_getDwarfmodeViewDims(lua_State *state)
lua_newtable(state);
Lua::TableInsert(state, "map_x1", dims.map_x1);
Lua::TableInsert(state, "map_x2", dims.map_x2);
Lua::TableInsert(state, "menu_x1", dims.menu_x1);
Lua::TableInsert(state, "menu_x2", dims.menu_x2);
Lua::TableInsert(state, "area_x1", dims.area_x1);
Lua::TableInsert(state, "area_x2", dims.area_x2);
Lua::TableInsert(state, "y1", dims.y1);
Lua::TableInsert(state, "y2", dims.y2);
Lua::TableInsert(state, "map_y1", dims.map_y1);
Lua::TableInsert(state, "map_y2", dims.map_y2);
Lua::TableInsert(state, "menu_on", dims.menu_on);
Lua::TableInsert(state, "area_on", dims.area_on);
Lua::TableInsert(state, "menu_forced", dims.menu_forced);
return 1;
}
@ -2373,6 +2364,11 @@ static int screen_getMousePos(lua_State *L)
return Lua::PushPosXY(L, Screen::getMousePos());
}
static int screen_getMousePixels(lua_State *L)
{
return Lua::PushPosXY(L, Screen::getMousePixels());
}
static int screen_getWindowSize(lua_State *L)
{
return Lua::PushPosXY(L, Screen::getWindowSize());
@ -2563,6 +2559,7 @@ static int screen_zoom(lua_State *L)
static const luaL_Reg dfhack_screen_funcs[] = {
{ "getMousePos", screen_getMousePos },
{ "getMousePixels", screen_getMousePixels },
{ "getWindowSize", screen_getWindowSize },
{ "paintTile", screen_paintTile },
{ "readTile", screen_readTile },

@ -35,7 +35,6 @@ distribution.
#include <stdio.h>
#include "tinythread.h"
#include "../plugins/uicommon.h"
/*
* Plugin loading functions

@ -143,13 +143,10 @@ namespace DFHack
static const int MENU_WIDTH = 30;
struct DwarfmodeDims {
int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2;
int y1, y2;
int map_x1, map_x2;
int map_y1, map_y2;
bool menu_on, area_on, menu_forced;
rect2d map() { return mkrect_xy(map_x1, map_y1, map_x2, map_y2); }
rect2d menu() { return mkrect_xy(menu_x1, y1, menu_x2, y2); }
};
DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims();
@ -198,18 +195,6 @@ namespace DFHack
/// get the size of the window buffer
DFHACK_EXPORT bool getWindowSize(int32_t & width, int32_t & height);
/**
*Menu width:
*3:3 - menu and area map closed
*2:3 - menu open single width
*1:3 - menu open double width
*1:2 - menu and area map open
*2:2 - area map open
*/
DFHACK_EXPORT bool getMenuWidth(uint8_t & menu_width, uint8_t & area_map_width);
DFHACK_EXPORT bool setMenuWidth(const uint8_t menu_width, const uint8_t area_map_width);
namespace Hooks {
GUI_HOOK_DECLARE(depth_at, int, (int32_t x, int32_t y));
GUI_HOOK_DECLARE(dwarfmode_view_dims, DwarfmodeDims, ());

@ -7,8 +7,6 @@ local utils = require('utils')
local dscreen = dfhack.screen
local getval = utils.getval
USE_GRAPHICS = dscreen.inGraphicsMode()
local to_pen = dfhack.pen.parse
CLEAR_PEN = to_pen{tile=909, ch=32, fg=0, bg=0}
@ -599,6 +597,7 @@ end
Screen = defclass(Screen, View)
Screen.text_input_mode = false
Screen.request_full_screen_refresh = false
function Screen:postinit()
self:onResize(dscreen.getWindowSize())
@ -624,7 +623,10 @@ function Screen:renderParent()
else
dscreen.clear()
end
df.global.gps.force_full_display_count = 1
if Screen.request_full_screen_refresh then
df.global.gps.force_full_display_count = 1
Screen.request_full_screen_refresh = false
end
end
function Screen:sendInputToParent(...)
@ -659,6 +661,8 @@ function Screen:dismiss()
if self._native then
dscreen.dismiss(self)
end
-- don't leave artifacts behind on the parent screen when we disappear
Screen.request_full_screen_refresh = true
end
function Screen:onDismiss()

@ -258,8 +258,8 @@ function BuildingDialog:onSubmitItem(idx, item)
end
function BuildingDialog:onInput(keys)
if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then
if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then
if keys.LEAVESCREEN then
if self.subviews.back.visible then
self:onGoBack()
else
self:dismiss()
@ -267,9 +267,9 @@ function BuildingDialog:onInput(keys)
self.on_cancel()
end
end
else
self:inputToSubviews(keys)
return true
end
self:inputToSubviews(keys)
end
function showBuildingPrompt(title, prompt, on_select, on_cancel, build_filter)

@ -48,7 +48,7 @@ end
function MessageBox:onRenderFrame(dc,rect)
MessageBox.super.onRenderFrame(self,dc,rect)
if self.on_accept then
dc:seek(rect.x1+2,rect.y2):key('LEAVESCREEN'):string('/'):key('MENU_CONFIRM')
dc:seek(rect.x1+2,rect.y2):key('LEAVESCREEN'):string('/'):key('SELECT')
end
end
@ -59,19 +59,16 @@ function MessageBox:onDestroy()
end
function MessageBox:onInput(keys)
if keys.MENU_CONFIRM then
if keys.SELECT or keys.LEAVESCREEN then
self:dismiss()
if self.on_accept then
if keys.SELECT and self.on_accept then
self.on_accept()
end
elseif keys.LEAVESCREEN or (keys.SELECT and not self.on_accept) then
self:dismiss()
if self.on_cancel then
elseif keys.LEAVESCREEN and self.on_cancel then
self.on_cancel()
end
else
self:inputToSubviews(keys)
return true
end
return self:inputToSubviews(keys)
end
function showMessage(title, text, tcolor, on_close)
@ -132,14 +129,15 @@ function InputBox:onInput(keys)
if self.on_input then
self.on_input(self.subviews.edit.text)
end
return true
elseif keys.LEAVESCREEN then
self:dismiss()
if self.on_cancel then
self.on_cancel()
end
else
self:inputToSubviews(keys)
return true
end
return self:inputToSubviews(keys)
end
function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width)
@ -238,9 +236,9 @@ function ListBox:onInput(keys)
if self.on_cancel then
self.on_cancel()
end
else
self:inputToSubviews(keys)
return true
end
return self:inputToSubviews(keys)
end
function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width, filter)

@ -71,30 +71,9 @@ end
function getPanelLayout()
local dims = dfhack.gui.getDwarfmodeViewDims()
local area_pos = df.global.ui_menu_width[1]
local menu_pos = df.global.ui_menu_width[0]
if dims.menu_forced then
menu_pos = area_pos - 1
end
local rv = {
menu_pos = menu_pos,
area_pos = area_pos,
map = gui.mkdims_xy(dims.map_x1, dims.map_y1, dims.map_x2, dims.map_y2),
return {
map=gui.mkdims_xy(dims.map_x1, dims.map_y1, dims.map_x2, dims.map_y2),
}
if dims.menu_forced then
rv.menu_forced = true
end
if dims.menu_on then
rv.menu = gui.mkdims_xy(dims.menu_x1, dims.y1, dims.menu_x2, dims.y2)
end
if dims.area_on then
rv.area_map = gui.mkdims_xy(dims.area_x1, dims.y1, dims.area_x2, dims.y2)
end
return rv
end
function getCursorPos()
@ -267,6 +246,10 @@ function Viewport:reveal(target,gap,max_scroll,scroll_gap,scroll_z)
end
MOVEMENT_KEYS = {
KEYBOARD_CURSOR_UP = { 0, -1, 0 }, KEYBOARD_CURSOR_DOWN = { 0, 1, 0 },
KEYBOARD_CURSOR_LEFT = { -1, 0, 0 }, KEYBOARD_CURSOR_RIGHT = { 1, 0, 0 },
KEYBOARD_CURSOR_UP_FAST = { 0, -1, 0, true }, KEYBOARD_CURSOR_DOWN_FAST = { 0, 1, 0, true },
KEYBOARD_CURSOR_LEFT_FAST = { -1, 0, 0, true }, KEYBOARD_CURSOR_RIGHT_FAST = { 1, 0, 0, true },
CURSOR_UP = { 0, -1, 0 }, CURSOR_DOWN = { 0, 1, 0 },
CURSOR_LEFT = { -1, 0, 0 }, CURSOR_RIGHT = { 1, 0, 0 },
CURSOR_UPLEFT = { -1, -1, 0 }, CURSOR_UPRIGHT = { 1, -1, 0 },
@ -553,66 +536,4 @@ function MenuOverlay:renderMapOverlay(get_overlay_char_fn, bounds_rect)
end
end
--fakes a "real" workshop sidebar menu, but on exactly selected workshop
WorkshopOverlay = defclass(WorkshopOverlay, MenuOverlay)
WorkshopOverlay.focus_path="WorkshopOverlay"
WorkshopOverlay.ATTRS={
workshop=DEFAULT_NIL,
}
function WorkshopOverlay:onAboutToShow(below)
WorkshopOverlay.super.onAboutToShow(self,below)
if df.global.world.selected_building ~= self.workshop then
error("The workshop overlay tried to show up for incorrect workshop")
end
end
function WorkshopOverlay:onInput(keys)
local allowedKeys={ --TODO add options: job management, profile, etc...
"CURSOR_RIGHT","CURSOR_RIGHT_FAST","CURSOR_LEFT","CURSOR_LEFT_FAST","CURSOR_UP","CURSOR_UP_FAST","CURSOR_DOWN","CURSOR_DOWN_FAST",
"CURSOR_UPRIGHT","CURSOR_UPRIGHT_FAST","CURSOR_UPLEFT","CURSOR_UPLEFT_FAST","CURSOR_DOWNRIGHT","CURSOR_DOWNRIGHT_FAST","CURSOR_DOWNLEFT","CURSOR_DOWNLEFT_FAST",
"CURSOR_UP_Z","CURSOR_DOWN_Z","DESTROYBUILDING","CHANGETAB","SUSPENDBUILDING"}
if keys.LEAVESCREEN then
self:dismiss()
self:sendInputToParent('LEAVESCREEN')
elseif keys.CHANGETAB then
self:sendInputToParent("CHANGETAB")
self:inputToSubviews(keys)
self:updateLayout()
else
for _,name in ipairs(allowedKeys) do
if keys[name] then
self:sendInputToParent(name)
break
end
end
self:inputToSubviews(keys)
end
if df.global.world.selected_building ~= self.workshop then
self:dismiss()
return
end
end
function WorkshopOverlay:onGetSelectedBuilding()
return self.workshop
end
local function is_slated_for_remove( bld )
for i,v in ipairs(bld.jobs) do
if v.job_type==df.job_type.DestroyBuilding then
return true
end
end
return false
end
function WorkshopOverlay:render(dc)
self:renderParent()
if df.global.world.selected_building ~= self.workshop then
return
end
if is_slated_for_remove(self.workshop) then
return
end
WorkshopOverlay.super.render(self, dc)
end
return _ENV

@ -255,8 +255,8 @@ function MaterialDialog:onSubmitItem(idx, item)
end
function MaterialDialog:onInput(keys)
if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then
if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then
if keys.LEAVESCREEN then
if self.subviews.back.visible then
self:onGoBack()
else
self:dismiss()
@ -264,9 +264,9 @@ function MaterialDialog:onInput(keys)
self.on_cancel()
end
end
else
self:inputToSubviews(keys)
return true
end
return self:inputToSubviews(keys)
end
function showMaterialPrompt(title, prompt, on_select, on_cancel, mat_filter)

@ -8,6 +8,7 @@ local utils = require('utils')
local dscreen = dfhack.screen
local getval = utils.getval
local to_pen = dfhack.pen.parse
local function show_view(view,vis)
if view then
@ -25,16 +26,13 @@ end
STANDARDSCROLL = {
STANDARDSCROLL_UP = -1,
KEYBOARD_CURSOR_UP = -1,
STANDARDSCROLL_DOWN = 1,
KEYBOARD_CURSOR_DOWN = 1,
STANDARDSCROLL_PAGEUP = '-page',
KEYBOARD_CURSOR_UP_FAST = '-page',
STANDARDSCROLL_PAGEDOWN = '+page',
}
SECONDSCROLL = {
SECONDSCROLL_UP = -1,
SECONDSCROLL_DOWN = 1,
SECONDSCROLL_PAGEUP = '-page',
SECONDSCROLL_PAGEDOWN = '+page',
KEYBOARD_CURSOR_DOWN_FAST = '+page',
}
------------
@ -365,8 +363,10 @@ function Panel:setKeyboardDragEnabled(enabled)
return
end
if enabled then
local kbd_get_pos = function() return {x=0, y=0} end
Panel_begin_drag(self, kbd_get_pos())
local kbd_get_pos = function()
return {x=self.frame_rect.x1, y=self.frame_rect.y1}
end
Panel_begin_drag(self)
self.kbd_get_pos = kbd_get_pos
else
Panel_end_drag(self)
@ -441,6 +441,9 @@ end
-- if self.autoarrange_subviews is true, lay out visible subviews vertically,
-- adding gaps between widgets according to self.autoarrange_gap.
function Panel:postUpdateLayout()
-- don't leave artifacts behind on the parent screen when we move
gui.Screen.request_full_screen_refresh = true
if not self.autoarrange_subviews then return end
local gap = self.autoarrange_gap
@ -465,7 +468,7 @@ function Panel:onRenderFrame(dc, rect)
gui.paint_frame(dc, rect, self.frame_style, self.frame_title)
if self.kbd_get_pos then
local pos = self.kbd_get_pos()
local pen = dfhack.pen.parse{fg=COLOR_GREEN, bg=COLOR_BLACK}
local pen = to_pen{fg=COLOR_GREEN, bg=COLOR_BLACK}
dc:seek(pos.x, pos.y):pen(pen):char(string.char(0xDB))
end
if self.drag_offset and not self.kbd_get_pos
@ -654,6 +657,12 @@ function EditField:onRenderBody(dc)
dc:string((' '):rep(dc.clip_x2 - dc.x))
end
function EditField:insert(text)
local old = self.text
self:setText(old:sub(1,self.cursor-1)..text..old:sub(self.cursor),
self.cursor + #text)
end
function EditField:onInput(keys)
if not self.focus then
-- only react to our hotkey
@ -676,22 +685,20 @@ function EditField:onInput(keys)
return true
end
if keys.SELECT then
if keys.SELECT or keys.CUSTOM_SHIFT_ENTER then
if self.key then
self:setFocus(false)
end
if self.on_submit then
self.on_submit(self.text)
return true
end
return not not self.key
elseif keys.SEC_SELECT then
if self.key then
self:setFocus(false)
end
if self.on_submit2 then
self.on_submit2(self.text)
return true
if keys.CUSTOM_SHIFT_ENTER then
if self.on_submit2 then
self.on_submit2(self.text)
return true
end
else
if self.on_submit then
self.on_submit(self.text)
return true
end
end
return not not self.key
elseif keys._MOUSE_L then
@ -712,8 +719,7 @@ function EditField:onInput(keys)
else
local cv = string.char(keys._STRING)
if not self.on_char or self.on_char(cv, old) then
self:setText(old:sub(1,self.cursor-1)..cv..old:sub(self.cursor),
self.cursor + 1)
self:insert(cv)
elseif self.on_char then
return self.modal
end
@ -722,25 +728,25 @@ function EditField:onInput(keys)
self.on_change(self.text, old)
end
return true
elseif keys.CURSOR_LEFT then
elseif keys.KEYBOARD_CURSOR_LEFT then
self:setCursor(self.cursor - 1)
return true
elseif keys.A_MOVE_W_DOWN then -- Ctrl-Left (end of prev word)
elseif keys.CUSTOM_CTRL_B then -- back one word
local _, prev_word_end = self.text:sub(1, self.cursor-1):
find('.*[%w_%-][^%w_%-]')
find('.*[%w_%-][^%w_%-]')
self:setCursor(prev_word_end or 1)
return true
elseif keys.A_CARE_MOVE_W then -- Alt-Left (home)
elseif keys.CUSTOM_CTRL_A then -- home
self:setCursor(1)
return true
elseif keys.CURSOR_RIGHT then
elseif keys.KEYBOARD_CURSOR_RIGHT then
self:setCursor(self.cursor + 1)
return true
elseif keys.A_MOVE_E_DOWN then -- Ctrl-Right (beginning of next word)
elseif keys.CUSTOM_CTRL_F then -- forward one word
local _,next_word_start = self.text:find('[^%w_%-][%w_%-]', self.cursor)
self:setCursor(next_word_start)
return true
elseif keys.A_CARE_MOVE_E then -- Alt-Right (end)
elseif keys.CUSTOM_CTRL_E then -- end
self:setCursor()
return true
end
@ -759,14 +765,12 @@ 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
init_table.frame.w = init_table.frame.w or 2
end
function Scrollbar:init()
@ -824,36 +828,95 @@ local function scrollbar_is_visible(scrollbar)
return scrollbar.elems_per_page < scrollbar.num_elems
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)
local SCROLLBAR_UP_LEFT_PEN = to_pen{tile=922, ch=47, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_UP_RIGHT_PEN = to_pen{tile=923, ch=92, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_DOWN_LEFT_PEN = to_pen{tile=946, ch=92, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_DOWN_RIGHT_PEN = to_pen{tile=947, ch=47, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_UP_LEFT_PEN = to_pen{tile=930, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_UP_RIGHT_PEN = to_pen{tile=931, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_LEFT_PEN = to_pen{tile=954, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_RIGHT_PEN = to_pen{tile=955, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_CENTER_UP_LEFT_PEN = to_pen{tile=932, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_CENTER_UP_RIGHT_PEN = to_pen{tile=933, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_CENTER_DOWN_LEFT_PEN = to_pen{tile=960, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_CENTER_DOWN_RIGHT_PEN = to_pen{tile=961, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_CENTER_LEFT_PEN = to_pen{tile=940, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_CENTER_RIGHT_PEN = to_pen{tile=941, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_DOWN_LEFT_PEN = to_pen{tile=966, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_DOWN_RIGHT_PEN = to_pen{tile=967, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
local SCROLLBAR_UP_LEFT_HOVER_PEN = to_pen{tile=924, ch=47, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_UP_RIGHT_HOVER_PEN = to_pen{tile=925, ch=92, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_DOWN_LEFT_HOVER_PEN = to_pen{tile=936, ch=92, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_DOWN_RIGHT_HOVER_PEN = to_pen{tile=937, ch=47, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_UP_LEFT_HOVER_PEN = to_pen{tile=930, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_UP_RIGHT_HOVER_PEN = to_pen{tile=931, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_LEFT_HOVER_PEN = to_pen{tile=954, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_RIGHT_HOVER_PEN = to_pen{tile=955, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_CENTER_UP_LEFT_HOVER_PEN = to_pen{tile=956, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_CENTER_UP_RIGHT_HOVER_PEN = to_pen{tile=957, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_CENTER_DOWN_LEFT_HOVER_PEN = to_pen{tile=968, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_CENTER_DOWN_RIGHT_HOVER_PEN = to_pen{tile=969, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_CENTER_LEFT_HOVER_PEN = to_pen{tile=942, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_CENTER_RIGHT_HOVER_PEN = to_pen{tile=943, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_DOWN_LEFT_HOVER_PEN = to_pen{tile=966, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_DOWN_RIGHT_HOVER_PEN = to_pen{tile=967, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
local SCROLLBAR_BAR_BG_LEFT_PEN = to_pen{tile=934, ch=176, fg=COLOR_DARKGREY, bg=COLOR_BLACK}
local SCROLLBAR_BAR_BG_RIGHT_PEN = to_pen{tile=935, ch=176, fg=COLOR_DARKGREY, bg=COLOR_BLACK}
function Scrollbar:onRenderBody(dc)
-- don't draw if all elements are visible
if not scrollbar_is_visible(self) 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)
-- determine which elements should be highlighted
local _,hover_y = self:getMousePos()
local hover_up, hover_down, hover_bar = false, false, false
if hover_y == 0 then
hover_up = true
elseif hover_y == dc.height-1 then
hover_down = true
elseif hover_y then
hover_bar = true
end
-- render up arrow
dc:seek(0, 0)
dc:char(nil, hover_up and SCROLLBAR_UP_LEFT_HOVER_PEN or SCROLLBAR_UP_LEFT_PEN)
dc:char(nil, hover_up and SCROLLBAR_UP_RIGHT_HOVER_PEN or SCROLLBAR_UP_RIGHT_PEN)
-- render scrollbar body
local starty = self.bar_offset + 1
local endy = self.bar_offset + self.bar_height
local midy = (starty + endy)/2
for y=1,dc.height-2 do
dc:seek(0, y)
if y >= starty and y <= endy then
dc:char(BAR_CHAR, self.fg)
if y == starty and y <= midy - 1 then
dc:char(nil, hover_bar and SCROLLBAR_BAR_UP_LEFT_HOVER_PEN or SCROLLBAR_BAR_UP_LEFT_PEN)
dc:char(nil, hover_bar and SCROLLBAR_BAR_UP_RIGHT_HOVER_PEN or SCROLLBAR_BAR_UP_RIGHT_PEN)
elseif y == midy - 0.5 then
dc:char(nil, hover_bar and SCROLLBAR_BAR_CENTER_UP_LEFT_HOVER_PEN or SCROLLBAR_BAR_CENTER_UP_LEFT_PEN)
dc:char(nil, hover_bar and SCROLLBAR_BAR_CENTER_UP_RIGHT_HOVER_PEN or SCROLLBAR_BAR_CENTER_UP_RIGHT_PEN)
elseif y == midy + 0.5 then
dc:char(nil, hover_bar and SCROLLBAR_BAR_CENTER_DOWN_LEFT_HOVER_PEN or SCROLLBAR_BAR_CENTER_DOWN_LEFT_PEN)
dc:char(nil, hover_bar and SCROLLBAR_BAR_CENTER_DOWN_RIGHT_HOVER_PEN or SCROLLBAR_BAR_CENTER_DOWN_RIGHT_PEN)
elseif y == midy then
dc:char(nil, hover_bar and SCROLLBAR_BAR_CENTER_LEFT_HOVER_PEN or SCROLLBAR_BAR_CENTER_LEFT_PEN)
dc:char(nil, hover_bar and SCROLLBAR_BAR_CENTER_RIGHT_HOVER_PEN or SCROLLBAR_BAR_CENTER_RIGHT_PEN)
elseif y == endy and y >= midy + 1 then
dc:char(nil, hover_bar and SCROLLBAR_BAR_DOWN_LEFT_HOVER_PEN or SCROLLBAR_BAR_DOWN_LEFT_PEN)
dc:char(nil, hover_bar and SCROLLBAR_BAR_DOWN_RIGHT_HOVER_PEN or SCROLLBAR_BAR_DOWN_RIGHT_PEN)
else
dc:char(nil, hover_bar and SCROLLBAR_BAR_LEFT_HOVER_PEN or SCROLLBAR_BAR_LEFT_PEN)
dc:char(nil, hover_bar and SCROLLBAR_BAR_RIGHT_HOVER_PEN or SCROLLBAR_BAR_RIGHT_PEN)
end
else
dc:char(BAR_BG_CHAR, self.bg)
dc:char(nil, SCROLLBAR_BAR_BG_LEFT_PEN)
dc:char(nil, SCROLLBAR_BAR_BG_RIGHT_PEN)
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)
-- render down arrow
dc:seek(0, dc.height-1)
dc:char(nil, hover_down and SCROLLBAR_DOWN_LEFT_HOVER_PEN or SCROLLBAR_DOWN_LEFT_PEN)
dc:char(nil, hover_down and SCROLLBAR_DOWN_RIGHT_HOVER_PEN or SCROLLBAR_DOWN_RIGHT_PEN)
if not self.on_scroll then return end
-- manage state for dragging and continuous scrolling
if self.is_dragging then
@ -877,10 +940,26 @@ function Scrollbar:onRenderBody(dc)
end
function Scrollbar:onInput(keys)
if not keys._MOUSE_L_DOWN or not self.on_scroll
or not scrollbar_is_visible(self) then
if not self.on_scroll or not scrollbar_is_visible(self) then
return false
end
if self.parent_view:getMousePos() then
if keys.CONTEXT_SCROLL_UP then
self.on_scroll('up_small')
return true
elseif keys.CONTEXT_SCROLL_DOWN then
self.on_scroll('down_small')
return true
elseif keys.CONTEXT_SCROLL_PAGEUP then
self.on_scroll('up_large')
return true
elseif keys.CONTEXT_SCROLL_PAGEDOWN then
self.on_scroll('down_large')
return true
end
end
if not keys._MOUSE_L_DOWN then return false end
local _,y = self:getMousePos()
if not y then return false end
local scroll_spec = nil
@ -988,13 +1067,13 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled)
if token.tile then
x = x + 1
if dc then
dc:char(nil, token.tile)
dc:tile(nil, token.tile)
end
end
if token.text or token.key then
local text = ''..(getval(token.text) or '')
local keypen = dfhack.pen.parse(token.key_pen or COLOR_LIGHTGREEN)
local keypen = to_pen(token.key_pen or COLOR_LIGHTGREEN)
if dc then
local tpen = getval(token.pen)
@ -1247,7 +1326,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-1)
local wrapped_text = self:getWrappedText(self.frame_body.width-3)
if not wrapped_text then return end
local text = {}
for _,line in ipairs(wrapped_text:split(NEWLINE)) do
@ -1622,12 +1701,14 @@ end
function List:submit()
if self.on_submit and #self.choices > 0 then
self.on_submit(self:getSelected())
return true
end
end
function List:submit2()
if self.on_submit2 and #self.choices > 0 then
self.on_submit2(self:getSelected())
return true
end
end
@ -1635,12 +1716,10 @@ function List:onInput(keys)
if self:inputToSubviews(keys) then
return true
end
if self.on_submit and keys.SELECT then
self:submit()
return true
elseif self.on_submit2 and keys.SEC_SELECT then
self:submit2()
return true
if keys.SELECT then
return self:submit()
elseif keys.CUSTOM_SHIFT_ENTER then
return self:submit2()
elseif keys._MOUSE_L_DOWN then
local idx = self:getIdxUnderMouse()
if idx then

@ -76,6 +76,7 @@ void Burrows::clearUnits(df::burrow *burrow)
burrow->units.clear();
/* TODO: understand how this changes for v50
// Sync ui if active
if (ui && ui->main.mode == ui_sidebar_mode::Burrows &&
ui->burrows.in_add_units_mode && ui->burrows.sel_id == burrow->id)
@ -85,6 +86,7 @@ void Burrows::clearUnits(df::burrow *burrow)
for (size_t i = 0; i < sel.size(); i++)
sel[i] = false;
}
*/
}
bool Burrows::isAssignedUnit(df::burrow *burrow, df::unit *unit)
@ -113,6 +115,7 @@ void Burrows::setAssignedUnit(df::burrow *burrow, df::unit *unit, bool enable)
erase_from_vector(burrow->units, unit->id);
}
/* TODO: understand how this changes for v50
// Sync ui if active
if (ui && ui->main.mode == ui_sidebar_mode::Burrows &&
ui->burrows.in_add_units_mode && ui->burrows.sel_id == burrow->id)
@ -121,6 +124,7 @@ void Burrows::setAssignedUnit(df::burrow *burrow, df::unit *unit, bool enable)
if (idx >= 0)
ui->burrows.sel_units[idx] = enable;
}
*/
}
void Burrows::listBlocks(std::vector<df::map_block*> *pvec, df::burrow *burrow)

@ -61,6 +61,7 @@ using namespace DFHack;
#include "df/general_ref.h"
#include "df/global_objects.h"
#include "df/graphic.h"
#include "df/graphic_viewportst.h"
#include "df/historical_figure.h"
#include "df/interfacest.h"
#include "df/item_corpsepiecest.h"
@ -142,6 +143,7 @@ static std::map<virtual_identity*, getFocusStringHandler> getFocusStringHandlers
DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
{
/* TODO: understand how this changes for v50
using namespace df::enums::ui_sidebar_mode;
using df::global::ui_workshop_in_add;
@ -314,7 +316,6 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
focus += "/List";
break;
/* TODO: understand how this changes for v50
case Hauling:
if (ui->hauling.in_assign_vehicle)
{
@ -353,11 +354,11 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
focus += "/Select/" + tag;
}
break;
*/
default:
break;
}
*/
}
/* TODO: understand how this changes for v50
@ -666,13 +667,14 @@ bool Gui::cursor_hotkey(df::viewscreen *top)
bool Gui::workshop_job_hotkey(df::viewscreen *top)
{
if (!dwarfmode_hotkey(top))
return false;
/* TODO: understand how this changes for v50
using namespace ui_sidebar_mode;
using df::global::ui_workshop_in_add;
using df::global::ui_workshop_job_cursor;
if (!dwarfmode_hotkey(top))
return false;
switch (ui->main.mode) {
case QueryBuilding:
{
@ -698,16 +700,18 @@ bool Gui::workshop_job_hotkey(df::viewscreen *top)
default:
return false;
}
*/ return false;
}
bool Gui::build_selector_hotkey(df::viewscreen *top)
{
using namespace ui_sidebar_mode;
using df::global::ui_build_selector;
if (!dwarfmode_hotkey(top))
return false;
/* TODO: understand how this changes for v50
using namespace ui_sidebar_mode;
using df::global::ui_build_selector;
switch (ui->main.mode) {
case Build:
{
@ -725,20 +729,23 @@ bool Gui::build_selector_hotkey(df::viewscreen *top)
default:
return false;
}
*/ return false;
}
bool Gui::view_unit_hotkey(df::viewscreen *top)
{
using df::global::ui_selected_unit;
if (!dwarfmode_hotkey(top))
return false;
/* TODO: understand how this changes for v50
using df::global::ui_selected_unit;
if (ui->main.mode != ui_sidebar_mode::ViewUnits)
return false;
if (!ui_selected_unit) // allow missing
return false;
return vector_get(world->units.active, *ui_selected_unit) != NULL;
*/ return false;
}
bool Gui::unit_inventory_hotkey(df::viewscreen *top)
@ -825,6 +832,7 @@ df::job *Gui::getSelectedJob(color_ostream &out, bool quiet)
df::unit *Gui::getAnyUnit(df::viewscreen *top)
{
/* TODO: understand how this changes for v50
using namespace ui_sidebar_mode;
using df::global::ui_look_cursor;
using df::global::ui_look_list;
@ -833,7 +841,6 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top)
using df::global::ui_building_assign_units;
using df::global::ui_building_item_cursor;
/* TODO: understand how this changes for v50
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_unitst, top))
{
return screen->unit;
@ -1107,13 +1114,13 @@ df::unit *Gui::getSelectedUnit(color_ostream &out, bool quiet)
df::item *Gui::getAnyItem(df::viewscreen *top)
{
/* TODO: understand how this changes for v50
using namespace ui_sidebar_mode;
using df::global::ui_look_cursor;
using df::global::ui_look_list;
using df::global::ui_unit_view_mode;
using df::global::ui_building_item_cursor;
/* TODO: understand how this changes for v50
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_textviewerst, top))
{
// return the main item if the parent screen is a viewscreen_itemst
@ -1247,11 +1254,11 @@ df::item *Gui::getSelectedItem(color_ostream &out, bool quiet)
df::building *Gui::getAnyBuilding(df::viewscreen *top)
{
/* TODO: understand how this changes for v50
using namespace ui_sidebar_mode;
using df::global::ui_look_list;
using df::global::ui_look_cursor;
/* TODO: understand how this changes for v50
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_buildinglistst, top))
return vector_get(screen->buildings, screen->cursor);
@ -1323,10 +1330,12 @@ df::plant *Gui::getAnyPlant(df::viewscreen *top)
if (!cursor || !ui || !world)
return nullptr;
/* TODO: understand how this changes for v50
if (ui->main.mode == ui_sidebar_mode::LookAround)
{
return Maps::getPlantAtTile(cursor->x, cursor->y, cursor->z);
}
*/
}
return nullptr;
@ -1918,47 +1927,14 @@ Gui::DwarfmodeDims getDwarfmodeViewDims_default()
{
Gui::DwarfmodeDims dims;
auto ws = Screen::getWindowSize();
dims.y1 = 1;
dims.y2 = ws.y-2;
dims.map_x1 = 1;
dims.map_x2 = ws.x-2;
dims.map_y1 = dims.y1;
dims.map_y2 = dims.y2;
dims.area_x1 = dims.area_x2 = dims.menu_x1 = dims.menu_x2 = -1;
dims.menu_forced = false;
int menu_pos = (ui_menu_width ? (*ui_menu_width)[0] : 2);
int area_pos = (ui_menu_width ? (*ui_menu_width)[1] : 3);
if (ui && ui->main.mode != ui_sidebar_mode::Default && ui->main.mode != ui_sidebar_mode::ArenaWeather && menu_pos >= area_pos)
{
dims.menu_forced = true;
menu_pos = area_pos-1;
}
dims.area_on = (area_pos < 3);
dims.menu_on = (menu_pos < area_pos);
bool use_graphics = Screen::inGraphicsMode();
auto dimx = use_graphics ? gps->main_viewport->dim_x : gps->dimx;
auto dimy = use_graphics ? gps->main_viewport->dim_y : gps->dimy;
if (dims.menu_on)
{
dims.menu_x2 = ws.x - 2;
dims.menu_x1 = dims.menu_x2 - Gui::MENU_WIDTH + 1;
if (menu_pos == 1)
dims.menu_x1 -= Gui::AREA_MAP_WIDTH + 1;
dims.map_x2 = dims.menu_x1 - 2;
}
if (dims.area_on)
{
dims.area_x2 = ws.x-2;
dims.area_x1 = dims.area_x2 - Gui::AREA_MAP_WIDTH + 1;
if (dims.menu_on)
dims.menu_x2 = dims.area_x1 - 2;
else
dims.map_x2 = dims.area_x1 - 2;
}
dims.map_x1 = 0;
dims.map_x2 = dimx - 1;
dims.map_y1 = 0;
dims.map_y2 = dimy - 1;
return dims;
}
@ -1977,7 +1953,9 @@ void Gui::resetDwarfmodeView(bool pause)
{
ui->follow_unit = -1;
ui->follow_item = -1;
/* TODO: understand how this changes for v50
ui->main.mode = ui_sidebar_mode::Default;
*/
}
if (selection_rect)
@ -2151,16 +2129,31 @@ df::coord Gui::getMousePos()
df::coord pos;
if (gps && gps->precise_mouse_x > -1) {
pos = getViewportPos();
/* TODO: understand how this changes for v50
pos.x += gps->mouse_x_pixel / tile_width;
pos.y += gps->mouse_y_pixel / tile_height;
*/
if (Screen::inGraphicsMode()) {
int32_t map_tile_pixels = gps->viewport_zoom_factor / 4;
pos.x += gps->precise_mouse_x / map_tile_pixels;
pos.y += gps->precise_mouse_y / map_tile_pixels;
} else {
pos.x += gps->mouse_x;
pos.y += gps->mouse_y;
}
}
if (!Maps::isValidTilePos(pos.x, pos.y, pos.z))
return df::coord();
return pos;
}
int getDepthAt_default (int32_t x, int32_t y)
{
auto &main_vp = gps->main_viewport;
if (x < 0 || x >= main_vp->dim_x || y < 0 || y >= main_vp->dim_y)
return 0;
const size_t num_viewports = gps->viewport.size();
const size_t index = (x * main_vp->dim_y) + y;
for (size_t depth = 0; depth < num_viewports; ++depth) {
if (gps->viewport[depth]->screentexpos_background[index])
return depth;
}
return 0;
}
@ -2183,17 +2176,3 @@ bool Gui::getWindowSize (int32_t &width, int32_t &height)
return false;
}
}
bool Gui::getMenuWidth(uint8_t &menu_width, uint8_t &area_map_width)
{
menu_width = (*ui_menu_width)[0];
area_map_width = (*ui_menu_width)[1];
return true;
}
bool Gui::setMenuWidth(const uint8_t menu_width, const uint8_t area_map_width)
{
(*ui_menu_width)[0] = menu_width;
(*ui_menu_width)[1] = area_map_width;
return true;
}

@ -922,11 +922,13 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
case general_ref_type::UNIT_HOLDER:
if (auto unit = ref->getUnit())
{
/* TODO: understand how this changes for v50
// Unit view sidebar holds inventory item pointers
if (ui->main.mode == ui_sidebar_mode::ViewUnits &&
(!ui_selected_unit ||
vector_get(world->units.active, *ui_selected_unit) == unit))
return false;
*/
for (int i = unit->inventory.size()-1; i >= 0; i--)
{

@ -116,7 +116,12 @@ bool Screen::inGraphicsMode()
}
static bool doSetTile_map(const Pen &pen, int x, int y) {
size_t max_index = gps->main_viewport->dim_x * gps->main_viewport->dim_y - 1;
size_t index = (x * gps->main_viewport->dim_y) + y;
if (index < 0 || index > max_index)
return false;
long texpos = pen.tile;
if (texpos == 0) {
texpos = init->font.large_font_texpos[(uint8_t)pen.ch];
@ -145,7 +150,10 @@ static bool doSetTile_default(const Pen &pen, int x, int y, bool map)
*screen = 0;
*texpos = 0;
*texpos_lower = 0;
*flag = 4; // remove SCREENTEXPOS_FLAG_ANCHOR_SUBORDINATE
gps->screentexpos_anchored[index] = 0;
// keep SCREENTEXPOS_FLAG_ANCHOR_SUBORDINATE so occluded anchored textures
// don't appear corrupted
*flag &= 4;
if (gps->top_in_use) {
screen = &gps->screen_top[index * 8];
@ -156,13 +164,22 @@ static bool doSetTile_default(const Pen &pen, int x, int y, bool map)
*screen = 0;
*texpos = 0;
*texpos_lower = 0;
*flag = 4; // remove SCREENTEXPOS_FLAG_ANCHOR_SUBORDINATE
gps->screentexpos_top_anchored[index] = 0;
*flag &= 4; // keep SCREENTEXPOS_FLAG_ANCHOR_SUBORDINATE
}
uint8_t fg = pen.fg | (pen.bold << 3);
uint8_t bg = pen.bg;
if (pen.tile_mode == Screen::Pen::CharColor)
*flag |= 2; // SCREENTEXPOS_FLAG_ADDCOLOR
else if (pen.tile_mode == Screen::Pen::TileColor)
else if (pen.tile_mode == Screen::Pen::TileColor) {
*flag |= 1; // SCREENTEXPOS_FLAG_GRAYSCALE
if (pen.tile_fg)
fg = pen.tile_fg;
if (pen.tile_bg)
bg = pen.tile_bg;
}
if (pen.tile && use_graphics) {
*texpos = pen.tile;
@ -171,15 +188,14 @@ static bool doSetTile_default(const Pen &pen, int x, int y, bool map)
*texpos_lower = 909;
}
// note that pen.bold currently (50.04) has no representation in the DF data
auto fg = &gps->uccolor[pen.fg][0];
auto bg = &gps->uccolor[pen.bg][0];
screen[1] = fg[0];
screen[2] = fg[1];
screen[3] = fg[2];
screen[4] = bg[0];
screen[5] = bg[1];
screen[6] = bg[2];
auto rgb_fg = &gps->uccolor[fg][0];
auto rgb_bg = &gps->uccolor[bg][0];
screen[1] = rgb_fg[0];
screen[2] = rgb_fg[1];
screen[3] = rgb_fg[2];
screen[4] = rgb_bg[0];
screen[5] = rgb_bg[1];
screen[6] = rgb_bg[2];
return true;
}
@ -198,39 +214,83 @@ bool Screen::paintTile(const Pen &pen, int x, int y, bool map)
return true;
}
static Pen doGetTile_default(int x, int y, bool map)
{
auto dim = Screen::getWindowSize();
if (x < 0 || x >= dim.x || y < 0 || y >= dim.y)
return Pen(0,0,0,-1);
static Pen doGetTile_map(int x, int y) {
size_t max_index = gps->main_viewport->dim_x * gps->main_viewport->dim_y - 1;
size_t index = (x * gps->main_viewport->dim_y) + y;
/* TODO: understand how this changes for v50
int index = x*dim.y + y;
auto screen = gps->screen + index*4;
if (screen[3] & 0x80)
return Pen(0,0,0,-1);
if (index < 0 || index > max_index)
return Pen(0, 0, 0, -1);
int tile = gps->main_viewport->screentexpos[index];
if (tile == 0)
tile = gps->main_viewport->screentexpos_item[index];
if (tile == 0)
tile = gps->main_viewport->screentexpos_building_one[index];
if (tile == 0)
tile = gps->main_viewport->screentexpos_background_two[index];
if (tile == 0)
tile = gps->main_viewport->screentexpos_background[index];
char ch = 0;
uint8_t fg = 0;
uint8_t bg = 0;
return Pen(ch, fg, bg, tile, false);
}
static uint8_t to_16_bit_color(uint8_t *rgb) {
for (uint8_t c = 0; c < 16; ++c) {
if (rgb[0] == gps->uccolor[c][0] &&
rgb[1] == gps->uccolor[c][1] &&
rgb[2] == gps->uccolor[c][2]) {
return c;
}
}
return 0;
}
Pen pen(
screen[0], screen[1], screen[2], screen[3]?true:false,
gps->screentexpos[index]
);
static Pen doGetTile_default(int x, int y, bool map) {
if (x < 0 || y < 0)
return Pen(0, 0, 0, -1);
if (pen.tile)
{
if (gps->screentexpos_grayscale[index])
{
pen.tile_mode = Screen::Pen::TileColor;
pen.tile_fg = gps->screentexpos_cf[index];
pen.tile_bg = gps->screentexpos_cbr[index];
}
else if (gps->screentexpos_addcolor[index])
{
pen.tile_mode = Screen::Pen::CharColor;
}
bool use_graphics = Screen::inGraphicsMode();
if (map && use_graphics)
return doGetTile_map(x, y);
size_t index = (x * gps->dimy) + y;
uint8_t *screen = &gps->screen[index * 8];
if (screen > gps->screen_limit)
return Pen(0, 0, 0, -1);
long *texpos = &gps->screentexpos[index];
uint32_t *flag = &gps->screentexpos_flag[index];
if (gps->top_in_use &&
(gps->screen_top[index * 8] ||
(use_graphics && gps->screentexpos_top[index]))) {
screen = &gps->screen_top[index * 8];
texpos = &gps->screentexpos_top[index];
flag = &gps->screentexpos_top_flag[index];
}
char ch = *screen;
uint8_t fg = to_16_bit_color(&screen[1]);
uint8_t bg = to_16_bit_color(&screen[4]);
int tile = 0;
if (use_graphics)
tile = *texpos;
if (*flag & 1) {
// TileColor
return Pen(ch, fg&7, bg, !!(fg&8), tile, fg, bg);
} else if (*flag & 2) {
// CharColor
return Pen(ch, fg, bg, tile, true);
}
return pen;
*/ return Pen(0,0,0,-1);
// AsIs
return Pen(ch, fg, bg, tile, false);
}
GUI_HOOK_DEFINE(Screen::Hooks::get_tile, doGetTile_default);

@ -1 +1 @@
Subproject commit 7debb9411b6df0709b295a8bb85b8dbada14b45d
Subproject commit dff467dccfe77fb1dd0468ac87989d2e08227592

@ -80,7 +80,7 @@ set_source_files_properties( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE )
#dfhack_plugin(3dveins 3dveins.cpp)
#dfhack_plugin(add-spatter add-spatter.cpp)
#dfhack_plugin(autobutcher autobutcher.cpp LINK_LIBRARIES lua)
dfhack_plugin(autobutcher autobutcher.cpp LINK_LIBRARIES lua)
#dfhack_plugin(autochop autochop.cpp)
#dfhack_plugin(autoclothing autoclothing.cpp)
#dfhack_plugin(autodump autodump.cpp)
@ -141,7 +141,7 @@ dfhack_plugin(hotkeys hotkeys.cpp LINK_LIBRARIES lua)
#dfhack_plugin(nestboxes nestboxes.cpp)
#dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static)
dfhack_plugin(overlay overlay.cpp LINK_LIBRARIES lua)
#dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua)
dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua)
#dfhack_plugin(petcapRemover petcapRemover.cpp)
#dfhack_plugin(plants plants.cpp)
#dfhack_plugin(probe probe.cpp)

@ -712,13 +712,20 @@ static bool hasValidMapPos(df::unit *unit) {
&& unit->pos.z < world->map.z_count;
}
// built cage defined as room (supposed to detect zoo cages)
// built cage in a zone (supposed to detect zoo cages)
static bool isInBuiltCageRoom(df::unit *unit) {
for (auto building : world->buildings.all) {
// !!! building->isRoom() returns true if the building can be made a room but currently isn't
// !!! except for coffins/tombs which always return false
// !!! using the bool is_room however gives the correct state/value
if (!building->is_room || building->getType() != df::building_type::Cage)
if (building->getType() != df::building_type::Cage)
continue;
bool in_zone = false;
for (auto relation : building->relations) {
if (relation->getType() == df::building_type::Civzone) {
in_zone = true;
break;
}
}
if (!in_zone)
continue;
df::building_cagest* cage = (df::building_cagest*)building;

@ -11,15 +11,15 @@ local widgets = require('gui.widgets')
HotspotMenuWidget = defclass(HotspotMenuWidget, overlay.OverlayWidget)
HotspotMenuWidget.ATTRS{
default_pos={x=1,y=2},
default_pos={x=2,y=2},
hotspot=true,
viewscreens={'dwarfmode'},
viewscreens='all',
overlay_onupdate_max_freq_seconds=0,
frame={w=4, h=3}
}
function HotspotMenuWidget:init()
self:addviews{widgets.Label{text={'!!!!', NEWLINE, '!!!!', NEWLINE, '!!!!'}}}
self:addviews{widgets.Label{text={'!DF!', NEWLINE, '!Ha!', NEWLINE, '!ck!'}}}
self.mouseover = false
end

@ -78,7 +78,9 @@ end
-- normalize "short form" viewscreen names to "long form"
local function normalize_viewscreen_name(vs_name)
if vs_name:match('viewscreen_.*st') then return vs_name end
if vs_name == 'all' or vs_name:match('viewscreen_.*st') then
return vs_name
end
return 'viewscreen_' .. vs_name .. 'st'
end
@ -177,6 +179,7 @@ end
local function do_disable(args, quiet)
local disable_fn = function(name, db_entry)
if db_entry.widget.always_enabled then return end
overlay_config[name].enabled = false
if db_entry.widget.hotspot then
active_hotspot_widgets[name] = nil
@ -244,7 +247,7 @@ local function load_widget(name, widget_class)
local config = overlay_config[name]
config.pos = sanitize_pos(config.pos or widget.default_pos)
widget.frame = make_frame(config.pos, widget.frame)
if config.enabled then
if config.enabled or widget.always_enabled then
do_enable(name, true, true)
else
config.enabled = false
@ -416,18 +419,24 @@ function update_hotspot_widgets()
end
end
-- not subject to trigger lock since these widgets are already filtered by
-- viewscreen
function update_viewscreen_widgets(vs_name, vs)
local function _update_viewscreen_widgets(vs_name, vs, now_ms)
local vs_widgets = active_viewscreen_widgets[vs_name]
if not vs_widgets then return end
local now_ms = dfhack.getTickCount()
now_ms = now_ms or dfhack.getTickCount()
for name,db_entry in pairs(vs_widgets) do
if do_update(name, db_entry, now_ms, vs) then return end
end
return now_ms
end
function feed_viewscreen_widgets(vs_name, keys)
-- not subject to trigger lock since these widgets are already filtered by
-- viewscreen
function update_viewscreen_widgets(vs_name, vs)
local now_ms = _update_viewscreen_widgets(vs_name, vs, nil)
_update_viewscreen_widgets('all', vs, now_ms)
end
local function _feed_viewscreen_widgets(vs_name, keys)
local vs_widgets = active_viewscreen_widgets[vs_name]
if not vs_widgets then return false end
for _,db_entry in pairs(vs_widgets) do
@ -439,16 +448,26 @@ function feed_viewscreen_widgets(vs_name, keys)
return false
end
function render_viewscreen_widgets(vs_name)
function feed_viewscreen_widgets(vs_name, keys)
return _feed_viewscreen_widgets(vs_name, keys) or
_feed_viewscreen_widgets('all', keys)
end
local function _render_viewscreen_widgets(vs_name, dc)
local vs_widgets = active_viewscreen_widgets[vs_name]
if not vs_widgets then return false end
local dc = gui.Painter.new()
dc = dc or gui.Painter.new()
for _,db_entry in pairs(vs_widgets) do
local w = db_entry.widget
detect_frame_change(w, function() w:render(dc) end)
end
end
function render_viewscreen_widgets(vs_name)
local dc = _render_viewscreen_widgets(vs_name, nil)
_render_viewscreen_widgets('all', dc)
end
-- called when the DF window is resized
function reposition_widgets()
local sr = get_screen_rect()
@ -461,7 +480,7 @@ end
-- OverlayWidget (base class of all overlay widgets) --
-- ------------------------------------------------- --
OverlayWidget = defclass(OverlayWidget, widgets.Widget)
OverlayWidget = defclass(OverlayWidget, widgets.Panel)
OverlayWidget.ATTRS{
name=DEFAULT_NIL, -- this is set by the framework to the widget name
default_pos={x=DEFAULT_X_POS, y=DEFAULT_Y_POS}, -- 1-based widget screen pos
@ -469,6 +488,7 @@ OverlayWidget.ATTRS{
hotspot=false, -- whether to call overlay_onupdate on all screens
viewscreens={}, -- override with associated viewscreen or list of viewscrens
overlay_onupdate_max_freq_seconds=5, -- throttle calls to overlay_onupdate
always_enabled=false, -- for overlays that should never be disabled
}
function OverlayWidget:init()

@ -2,7 +2,7 @@ local _ENV = mkmodule('plugins.pathable')
--[[
Native functions: (see Plugins.rst for details)
Native functions: (see docs/dev/Lua API.rst for details)
- paintScreen(cursor[,skip_unrevealed])

@ -1,82 +1,97 @@
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "DataFuncs.h"
#include "DataIdentity.h"
#include "Export.h"
#include "LuaTools.h"
#include "PluginManager.h"
#include "modules/Gui.h"
#include "modules/Maps.h"
#include "modules/Screen.h"
#include "df/world.h"
#include "Debug.h"
#include "LuaTools.h"
#include "PluginManager.h"
using namespace DFHack;
DFHACK_PLUGIN("pathable");
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(window_x);
REQUIRE_GLOBAL(window_y);
REQUIRE_GLOBAL(window_z);
REQUIRE_GLOBAL(world);
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands)
{
namespace DFHack {
DBG_DECLARE(pathable, log, DebugCategory::LINFO);
}
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) {
return CR_OK;
}
DFhackCExport command_result plugin_shutdown(color_ostream &out)
{
DFhackCExport command_result plugin_shutdown(color_ostream &out) {
return CR_OK;
}
static void paintScreen(df::coord cursor, bool skip_unrevealed = false)
{
auto dims = Gui::getDwarfmodeViewDims();
for (int y = dims.map_y1; y <= dims.map_y2; y++)
{
for (int x = dims.map_x1; x <= dims.map_x2; x++)
{
Screen::Pen cur_tile = Screen::readTile(x, y, true);
if (!cur_tile.valid())
continue;
static void paintScreen(df::coord target, bool skip_unrevealed = false) {
DEBUG(log).print("entering paintScreen\n");
bool use_graphics = Screen::inGraphicsMode();
int selected_tile_texpos = 0;
Screen::findGraphicsTile("CURSORS", 4, 3, &selected_tile_texpos);
df::coord map_pos(
*window_x + x - dims.map_x1,
*window_y + y - dims.map_y1,
*window_z
);
auto dims = Gui::getDwarfmodeViewDims().map();
for (int y = dims.first.y; y <= dims.second.y; ++y) {
for (int x = dims.first.x; x <= dims.second.x; ++x) {
df::coord map_pos(*window_x + x, *window_y + y, *window_z);
// Keep yellow cursor
if (map_pos == cursor)
if (!Maps::isValidTilePos(map_pos))
continue;
if (map_pos.x < 0 || map_pos.x >= world->map.x_count ||
map_pos.y < 0 || map_pos.y >= world->map.y_count ||
map_pos.z < 0 || map_pos.z >= world->map.z_count)
{
// don't overwrite the target tile
if (!use_graphics && map_pos == target) {
TRACE(log).print("skipping target tile\n");
continue;
}
if (skip_unrevealed && !Maps::isTileVisible(map_pos))
if (skip_unrevealed && !Maps::isTileVisible(map_pos)) {
TRACE(log).print("skipping hidden tile\n");
continue;
}
int color = Maps::canWalkBetween(cursor, map_pos) ? COLOR_GREEN : COLOR_RED;
DEBUG(log).print("scanning map tile at offset %d, %d\n", x, y);
Screen::Pen cur_tile = Screen::readTile(x, y, true);
DEBUG(log).print("tile data: ch=%d, fg=%d, bg=%d, bold=%s\n",
cur_tile.ch, cur_tile.fg, cur_tile.bg, cur_tile.bold ? "true" : "false");
DEBUG(log).print("tile data: tile=%d, tile_mode=%d, tile_fg=%d, tile_bg=%d\n",
cur_tile.tile, cur_tile.tile_mode, cur_tile.tile_fg, cur_tile.tile_bg);
if (cur_tile.fg && cur_tile.ch != ' ')
{
cur_tile.fg = color;
cur_tile.bg = 0;
}
else
{
cur_tile.fg = 0;
cur_tile.bg = color;
if (!cur_tile.valid()) {
DEBUG(log).print("cannot read tile at offset %d, %d\n", x, y);
continue;
}
cur_tile.bold = false;
bool can_walk = Maps::canWalkBetween(target, map_pos);
DEBUG(log).print("tile is %swalkable at offset %d, %d\n",
can_walk ? "" : "not ", x, y);
if (use_graphics) {
if (map_pos == target) {
cur_tile.tile = selected_tile_texpos;
} else{
cur_tile.tile = can_walk ? 779 : 782;
}
} else {
int color = can_walk ? COLOR_GREEN : COLOR_RED;
if (cur_tile.fg && cur_tile.ch != ' ') {
cur_tile.fg = color;
cur_tile.bg = 0;
} else {
cur_tile.fg = 0;
cur_tile.bg = color;
}
if (cur_tile.tile)
cur_tile.tile_mode = Screen::Pen::CharColor;
cur_tile.bold = false;
if (cur_tile.tile)
cur_tile.tile_mode = Screen::Pen::CharColor;
}
Screen::paintTile(cur_tile, x, y, true);
}

@ -1 +1 @@
Subproject commit e64d86728f25f22871e47beb52ad1177aef5e043
Subproject commit 5ec3a484e243954151cf21fd288311667bf15b61