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 keybinding add Ctrl-Shift-K gui/cp437-table
# an in-game init file editor # 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 # 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 # toggle the display of water level as 1-7 tiles
keybinding add Ctrl-W@dwarfmode|dungeonmode twaterlvl keybinding add Ctrl-W@dwarfmode|dungeonmode twaterlvl
# designate the whole vein for digging # designate the whole vein for digging
keybinding add Ctrl-V@dwarfmode digv #keybinding add Ctrl-V@dwarfmode digv
keybinding add Ctrl-Shift-V@dwarfmode "digv x" #keybinding add Ctrl-Shift-V@dwarfmode "digv x"
# clean the selected tile of blood etc # clean the selected tile of blood etc
keybinding add Ctrl-C spotclean #keybinding add Ctrl-C spotclean
# destroy the selected item # 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 # 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) # apply blueprints to the map (Alt-F for compatibility with LNP Quickfort)
keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort #keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort
keybinding add Alt-F@dwarfmode gui/quickfort #keybinding add Alt-F@dwarfmode gui/quickfort
# show information collected by dwarfmonitor # show information collected by dwarfmonitor
keybinding add Alt-M@dwarfmode/Default "dwarfmonitor prefs" #keybinding add Alt-M@dwarfmode/Default "dwarfmonitor prefs"
keybinding add Ctrl-F@dwarfmode/Default "dwarfmonitor stats" #keybinding add Ctrl-F@dwarfmode/Default "dwarfmonitor stats"
# set the zone or cage under the cursor as the default # 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 # 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) # 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 # 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 # 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) # 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 # 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 # 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 # 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 # 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 # 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 # q->stockpile - load and save stockpile settings out of game
keybinding add Alt-L@dwarfmode/QueryBuilding/Some/Stockpile "gui/stockpiles -load" #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-S@dwarfmode/QueryBuilding/Some/Stockpile "gui/stockpiles -save"
# q->workshop - duplicate the selected job # q->workshop - duplicate the selected job
keybinding add Ctrl-D job-duplicate #keybinding add Ctrl-D job-duplicate
# materials: q->workshop; b->select items # materials: q->workshop; b->select items
keybinding add Shift-A "job-material ALUNITE" #keybinding add Shift-A "job-material ALUNITE"
keybinding add Shift-M "job-material MICROCLINE" #keybinding add Shift-M "job-material MICROCLINE"
keybinding add Shift-D "job-material DACITE" #keybinding add Shift-D "job-material DACITE"
keybinding add Shift-R "job-material RHYOLITE" #keybinding add Shift-R "job-material RHYOLITE"
keybinding add Shift-I "job-material CINNABAR" #keybinding add Shift-I "job-material CINNABAR"
keybinding add Shift-B "job-material COBALTITE" #keybinding add Shift-B "job-material COBALTITE"
keybinding add Shift-O "job-material OBSIDIAN" #keybinding add Shift-O "job-material OBSIDIAN"
keybinding add Shift-T "job-material ORTHOCLASE" #keybinding add Shift-T "job-material ORTHOCLASE"
keybinding add Shift-G "job-material GLASS_GREEN" #keybinding add Shift-G "job-material GLASS_GREEN"
# sort units and items in the on-screen list # sort units and items in the on-screen list
keybinding add Alt-Shift-N "sort-units name" "sort-items description" #keybinding add Alt-Shift-N "sort-units name" "sort-items description"
keybinding add Alt-Shift-R "sort-units arrival" #keybinding add Alt-Shift-R "sort-units arrival"
keybinding add Alt-Shift-T "sort-units profession" "sort-items type material" #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-Q "sort-units squad_position" "sort-items quality"
# browse linked mechanisms # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # 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 # workflow front-end
keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow #keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow
keybinding add Alt-W@overallstatus "gui/workflow status" #keybinding add Alt-W@overallstatus "gui/workflow status"
# equivalent to the one above when gui/extended-status is enabled # 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 # 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 # 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 # 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-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-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 # # adv mode bindings #
##################### #####################
keybinding add Ctrl-A@dungeonmode/ConversationSpeak adv-rumors #keybinding add Ctrl-A@dungeonmode/ConversationSpeak adv-rumors
keybinding add Ctrl-B@dungeonmode adv-bodyswap #keybinding add Ctrl-B@dungeonmode adv-bodyswap
keybinding add Ctrl-Shift-B@dungeonmode "adv-bodyswap force" #keybinding add Ctrl-Shift-B@dungeonmode "adv-bodyswap force"
keybinding add Shift-O@dungeonmode gui/companion-order #keybinding add Shift-O@dungeonmode gui/companion-order
keybinding add Ctrl-T@dungeonmode gui/advfort #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) # 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": { "hotkeys.menu": {
"enabled": true "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, same version of DFHack. They are less flexible than scripts,
but used for complex or ongoing tasks because they run faster. but used for complex or ongoing tasks because they run faster.
:scripts: are Ruby or Lua scripts stored in ``hack/scripts/``. :scripts: are Ruby or Lua scripts stored in ``hack/scripts/`` or other
Because they don't need to be compiled, scripts are directories in the `script-paths`. Because they don't need to
more flexible about versions, and easier to distribute. be compiled, scripts are more flexible about versions, and
Most third-party DFHack addons are scripts. they are easier to distribute. Most third-party DFHack addons
are scripts.
All tools distributed with DFHack are documented `here <tools>`. 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` #. Pressing a key combination set up with `keybinding`
#. From one of several `init-files`, automatically #. From one of several `init-files`, automatically
#. Using `script` to run a batch of commands from a file #. 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 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: 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 #. 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` #. :file:`data/save/{world}/raw/objects`
For each of those directories, all matching init files will be executed in 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 On startup, DFHack looks for files of the form ``dfhack*.init`` (where ``*`` is
a placeholder for any string, including the empty string). 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. 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 run. By default, the following folders are searched, in order (relative to the
root DF folder): root DF folder):
1. :file:`data/save/{<region folder>}/raw/scripts` (only if a save is loaded) #. :file:`dfhack-config/scripts`
2. :file:`raw/scripts` #. :file:`data/save/{<region folder>}/raw/scripts` (only if a save is loaded)
3. :file:`hack/scripts` #. :file:`raw/scripts`
#. :file:`hack/scripts`
For example, if ``teleport`` is run, these folders are searched in order for 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. ``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 ## Fixes
## Misc Improvements ## 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 ## Documentation
- `overlay-dev-guide`: added troubleshooting tips and common development workflows - `overlay-dev-guide`: added troubleshooting tips and common development workflows
- added DFHack architecture diagrams to the dev intro - added DFHack architecture diagrams to the dev intro
## API ## API
- ``Gui::getDwarfmodeDims``: now only returns map viewport dimensions; menu dimensions are obsolete
## Lua ## Lua
- ``gui.View``: ``visible`` and ``active`` can now be functions that return a boolean - ``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.Panel``: new attributes to control window dragging and resizing with mouse or keyboard
- ``widgets.Window``: Panel subclass with attributes preset for top-level windows - ``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 ## Internals

@ -1011,7 +1011,7 @@ Fortress mode
* ``dfhack.gui.getDwarfmodeViewDims()`` * ``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. in the ``gui.dwarfmode`` module for a more Lua-friendly version.
* ``dfhack.gui.resetDwarfmodeView([pause])`` * ``dfhack.gui.resetDwarfmodeView([pause])``
@ -1121,7 +1121,8 @@ Other
* ``dfhack.gui.getDepthAt(x, y)`` * ``dfhack.gui.getDepthAt(x, y)``
Returns the distance from the z-level of the tile at map coordinates (x, y) to 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 Job module
---------- ----------
@ -2161,7 +2162,14 @@ Functions:
* ``dfhack.screen.getMousePos()`` * ``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()`` * ``dfhack.screen.inGraphicsMode()``
@ -2355,7 +2363,14 @@ Supported callbacks and fields are:
where the above painting functions work correctly. where the above painting functions work correctly.
If omitted, the screen is cleared; otherwise it should do that itself. 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()`` * ``function screen:onIdle()``
@ -2614,8 +2629,8 @@ and are only documented here for completeness:
Registers ``path`` as a `script path <script-paths>`. Registers ``path`` as a `script path <script-paths>`.
If ``search_before`` is passed and ``true``, the path will be searched before 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 the default paths (e.g. ``dfhack-config/scripts``, ``hack/scripts``); otherwise,
be searched after. it will be searched after.
Returns ``true`` if successful or ``false`` otherwise (e.g. if the path does Returns ``true`` if successful or ``false`` otherwise (e.g. if the path does
not exist or has already been registered). not exist or has already been registered).
@ -3632,11 +3647,6 @@ considered stable.
Misc 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`` * ``CLEAR_PEN``
The black pen used to clear the screen. 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. The pen to fill the outer frame with. Defaults to no fill.
.. _panel:
Panel class Panel class
----------- -----------
@ -4306,7 +4318,6 @@ Attributes:
If it returns false, the character is ignored. If it returns false, the character is ignored.
:on_change: Change notification callback; used as ``on_change(new_text,old_text)``. :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_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: 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 :key_sep: If specified, will be used to customize how the activation key is
displayed. See ``token.key_sep`` in the ``Label`` documentation below. 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: following keyboard hotkeys:
- Left/Right arrow: move the cursor one character to the left or right. - 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. - Ctrl-B/Ctrl-F: move the cursor one word back or forward.
- Alt-Left/Right arrow: move the cursor to the beginning/end of the text. - 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 Scrollbar class
--------------- ---------------
This Widget subclass implements mouse-interactive scrollbars whose bar sizes This Widget subclass implements mouse-interactive scrollbars whose bar sizes
represent the amount of content currently visible in an associated display 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 widget (like a `Label class`_ or a `List class`_). They are styled like scrollbars
like scrollbars used in the vanilla DF help screens, but they are configurable. used in vanilla DF.
Scrollbars have the following attributes: 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, :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", 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 "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 associated widget, and after scrolling is complete, the associated widget must
call ``scrollbar:update()`` with updated new display info. 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 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 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 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. with an empty list.
:on_submit: Enter key or mouse click callback; if specified, the list reacts to the :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)``. 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 :on_submit2: Shift-click callback; if specified, the list reacts to the click and
reacts to the key/click and calls it as ``on_submit2(index,choice)``. calls the callback as ``on_submit2(index,choice)``.
:row_height: Height of every row in text lines. :row_height: Height of every row in text lines.
:icon_width: If not *nil*, the specified number of character columns :icon_width: If not *nil*, the specified number of character columns
are reserved to the left of the list item for the icons. are reserved to the left of the list item for the icons.
@ -5455,11 +5485,11 @@ Scripts
:local: :local:
Any files with the ``.lua`` extension placed into the :file:`hack/scripts` folder Any files with the ``.lua`` extension placed into the :file:`hack/scripts` folder
are automatically made available as DFHack commands. The command corresponding to (or any other folder in your `script-paths`) are automatically made available as
a script is simply the script's filename, relative to the scripts folder, with DFHack commands. The command corresponding to a script is simply the script's
the extension omitted. For example: 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`` * :file:`hack/scripts/gui/teleport.lua` is invoked as ``gui/teleport``
.. note:: .. note::
@ -5476,12 +5506,6 @@ the extension omitted. For example:
a mod developer would want to run a script from the console, it should a mod developer would want to run a script from the console, it should
not be placed in this folder) 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 Scripts are read from disk when run for the first time, or if they have changed
since the last time they were run. since the last time they were run.
@ -5509,7 +5533,7 @@ General script API
* ``dfhack.run_script(name[,args...])`` * ``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 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. 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 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 functions and variables). This is similar to the built-in ``require()``, but
searches all script paths for the first matching ``name.lua`` file instead searches all `script-paths` for the first matching ``name.lua`` file instead
of searching the Lua library paths (like ``hack/lua``). of searching the Lua library paths (like ``hack/lua/``).
Most scripts can be made to support ``reqscript()`` without significant Most scripts can be made to support ``reqscript()`` without significant
changes (in contrast, ``require()`` requires the use of ``mkmodule()`` and 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`` 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)`` ``onInput(keys)``, ``onRenderFrame(dc, frame_rect)``, and ``onRenderBody(dc)``
functions work as normal, and they are called when the viewscreen that the 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 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 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, that running more often than you really need to will impact game FPS,
especially if your widget can run while the game is unpaused. 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 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: be:
1. ``own-scripts/`` 1. ``own-scripts/``
2. ``data/save/*/raw/scripts/`` 2. ``dfhack-config/scripts/``
3. ``raw/scripts/`` 3. ``data/save/*/raw/scripts/``
4. ``hack/scripts/`` 4. ``raw/scripts/``
5. ``hack/scripts/``
The structure of the game The structure of the game
------------------------- -------------------------

@ -12,7 +12,7 @@ watch list. Units will be ignored if they are:
* Untamed * Untamed
* Nicknamed (for custom protection; you can use the `rename` ``unit`` tool * Nicknamed (for custom protection; you can use the `rename` ``unit`` tool
individually, or `zone` ``nick`` for groups) 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 * Trained for war or hunting
Creatures who will not reproduce (because they're not interested in the Creatures who will not reproduce (because they're not interested in the

@ -7,4 +7,4 @@ pathable
:no-command: :no-command:
This plugin provides a Lua API, but no direct commands. See `pathable-api` for 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(); string df_path = this->p->getPath();
for (auto it = script_paths[0].begin(); it != script_paths[0].end(); ++it) for (auto it = script_paths[0].begin(); it != script_paths[0].end(); ++it)
dest->push_back(*it); dest->push_back(*it);
dest->push_back(df_path + "/dfhack-config/scripts");
if (df::global::world && isWorldLoaded()) { if (df::global::world && isWorldLoaded()) {
string save = World::ReadWorldFolder(); string save = World::ReadWorldFolder();
if (save.size()) if (save.size())
@ -2266,6 +2267,7 @@ bool Core::ncurses_wgetch(int in, int & out)
} }
if(in >= KEY_F(1) && in <= KEY_F(8)) if(in >= KEY_F(1) && in <= KEY_F(8))
{ {
/* TODO: understand how this changes for v50
int idx = in - KEY_F(1); int idx = in - KEY_F(1);
// FIXME: copypasta, push into a method! // FIXME: copypasta, push into a method!
if(df::global::ui && df::global::gview) if(df::global::ui && df::global::gview)
@ -2284,6 +2286,7 @@ bool Core::ncurses_wgetch(int in, int & out)
return true; return true;
} }
} }
*/
} }
out = in; out = in;
return true; return true;
@ -2451,6 +2454,7 @@ bool Core::SelectHotkey(int sym, int modifiers)
int idx = sym - SDL::K_F1; int idx = sym - SDL::K_F1;
if(idx >= 0 && idx < 8) if(idx >= 0 && idx < 8)
{ {
/* TODO: understand how this changes for v50
if (modifiers & 1) if (modifiers & 1)
idx += 8; idx += 8;
@ -2460,6 +2464,7 @@ bool Core::SelectHotkey(int sym, int modifiers)
{ {
cmd = df::global::ui->main.hotkeys[idx].name; cmd = df::global::ui->main.hotkeys[idx].name;
} }
*/
} }
} }
} }

@ -1436,17 +1436,8 @@ static int gui_getDwarfmodeViewDims(lua_State *state)
lua_newtable(state); lua_newtable(state);
Lua::TableInsert(state, "map_x1", dims.map_x1); Lua::TableInsert(state, "map_x1", dims.map_x1);
Lua::TableInsert(state, "map_x2", dims.map_x2); 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_y1", dims.map_y1);
Lua::TableInsert(state, "map_y2", dims.map_y2); 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; return 1;
} }
@ -2373,6 +2364,11 @@ static int screen_getMousePos(lua_State *L)
return Lua::PushPosXY(L, Screen::getMousePos()); 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) static int screen_getWindowSize(lua_State *L)
{ {
return Lua::PushPosXY(L, Screen::getWindowSize()); return Lua::PushPosXY(L, Screen::getWindowSize());
@ -2563,6 +2559,7 @@ static int screen_zoom(lua_State *L)
static const luaL_Reg dfhack_screen_funcs[] = { static const luaL_Reg dfhack_screen_funcs[] = {
{ "getMousePos", screen_getMousePos }, { "getMousePos", screen_getMousePos },
{ "getMousePixels", screen_getMousePixels },
{ "getWindowSize", screen_getWindowSize }, { "getWindowSize", screen_getWindowSize },
{ "paintTile", screen_paintTile }, { "paintTile", screen_paintTile },
{ "readTile", screen_readTile }, { "readTile", screen_readTile },

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

@ -143,13 +143,10 @@ namespace DFHack
static const int MENU_WIDTH = 30; static const int MENU_WIDTH = 30;
struct DwarfmodeDims { struct DwarfmodeDims {
int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2; int map_x1, map_x2;
int y1, y2;
int map_y1, map_y2; 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 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(); DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims();
@ -198,18 +195,6 @@ namespace DFHack
/// get the size of the window buffer /// get the size of the window buffer
DFHACK_EXPORT bool getWindowSize(int32_t & width, int32_t & height); 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 { namespace Hooks {
GUI_HOOK_DECLARE(depth_at, int, (int32_t x, int32_t y)); GUI_HOOK_DECLARE(depth_at, int, (int32_t x, int32_t y));
GUI_HOOK_DECLARE(dwarfmode_view_dims, DwarfmodeDims, ()); GUI_HOOK_DECLARE(dwarfmode_view_dims, DwarfmodeDims, ());

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

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

@ -48,7 +48,7 @@ end
function MessageBox:onRenderFrame(dc,rect) function MessageBox:onRenderFrame(dc,rect)
MessageBox.super.onRenderFrame(self,dc,rect) MessageBox.super.onRenderFrame(self,dc,rect)
if self.on_accept then 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
end end
@ -59,19 +59,16 @@ function MessageBox:onDestroy()
end end
function MessageBox:onInput(keys) function MessageBox:onInput(keys)
if keys.MENU_CONFIRM then if keys.SELECT or keys.LEAVESCREEN then
self:dismiss() self:dismiss()
if self.on_accept then if keys.SELECT and self.on_accept then
self.on_accept() self.on_accept()
end elseif keys.LEAVESCREEN and self.on_cancel then
elseif keys.LEAVESCREEN or (keys.SELECT and not self.on_accept) then
self:dismiss()
if self.on_cancel then
self.on_cancel() self.on_cancel()
end end
else return true
self:inputToSubviews(keys)
end end
return self:inputToSubviews(keys)
end end
function showMessage(title, text, tcolor, on_close) function showMessage(title, text, tcolor, on_close)
@ -132,14 +129,15 @@ function InputBox:onInput(keys)
if self.on_input then if self.on_input then
self.on_input(self.subviews.edit.text) self.on_input(self.subviews.edit.text)
end end
return true
elseif keys.LEAVESCREEN then elseif keys.LEAVESCREEN then
self:dismiss() self:dismiss()
if self.on_cancel then if self.on_cancel then
self.on_cancel() self.on_cancel()
end end
else return true
self:inputToSubviews(keys)
end end
return self:inputToSubviews(keys)
end end
function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width) 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 if self.on_cancel then
self.on_cancel() self.on_cancel()
end end
else return true
self:inputToSubviews(keys)
end end
return self:inputToSubviews(keys)
end end
function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width, filter) function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width, filter)

@ -71,30 +71,9 @@ end
function getPanelLayout() function getPanelLayout()
local dims = dfhack.gui.getDwarfmodeViewDims() local dims = dfhack.gui.getDwarfmodeViewDims()
local area_pos = df.global.ui_menu_width[1] return {
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), 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 end
function getCursorPos() function getCursorPos()
@ -267,6 +246,10 @@ function Viewport:reveal(target,gap,max_scroll,scroll_gap,scroll_z)
end end
MOVEMENT_KEYS = { 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_UP = { 0, -1, 0 }, CURSOR_DOWN = { 0, 1, 0 },
CURSOR_LEFT = { -1, 0, 0 }, CURSOR_RIGHT = { 1, 0, 0 }, CURSOR_LEFT = { -1, 0, 0 }, CURSOR_RIGHT = { 1, 0, 0 },
CURSOR_UPLEFT = { -1, -1, 0 }, CURSOR_UPRIGHT = { 1, -1, 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
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 return _ENV

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

@ -8,6 +8,7 @@ local utils = require('utils')
local dscreen = dfhack.screen local dscreen = dfhack.screen
local getval = utils.getval local getval = utils.getval
local to_pen = dfhack.pen.parse
local function show_view(view,vis) local function show_view(view,vis)
if view then if view then
@ -25,16 +26,13 @@ end
STANDARDSCROLL = { STANDARDSCROLL = {
STANDARDSCROLL_UP = -1, STANDARDSCROLL_UP = -1,
KEYBOARD_CURSOR_UP = -1,
STANDARDSCROLL_DOWN = 1, STANDARDSCROLL_DOWN = 1,
KEYBOARD_CURSOR_DOWN = 1,
STANDARDSCROLL_PAGEUP = '-page', STANDARDSCROLL_PAGEUP = '-page',
KEYBOARD_CURSOR_UP_FAST = '-page',
STANDARDSCROLL_PAGEDOWN = '+page', STANDARDSCROLL_PAGEDOWN = '+page',
} KEYBOARD_CURSOR_DOWN_FAST = '+page',
SECONDSCROLL = {
SECONDSCROLL_UP = -1,
SECONDSCROLL_DOWN = 1,
SECONDSCROLL_PAGEUP = '-page',
SECONDSCROLL_PAGEDOWN = '+page',
} }
------------ ------------
@ -365,8 +363,10 @@ function Panel:setKeyboardDragEnabled(enabled)
return return
end end
if enabled then if enabled then
local kbd_get_pos = function() return {x=0, y=0} end local kbd_get_pos = function()
Panel_begin_drag(self, kbd_get_pos()) return {x=self.frame_rect.x1, y=self.frame_rect.y1}
end
Panel_begin_drag(self)
self.kbd_get_pos = kbd_get_pos self.kbd_get_pos = kbd_get_pos
else else
Panel_end_drag(self) Panel_end_drag(self)
@ -441,6 +441,9 @@ end
-- if self.autoarrange_subviews is true, lay out visible subviews vertically, -- if self.autoarrange_subviews is true, lay out visible subviews vertically,
-- adding gaps between widgets according to self.autoarrange_gap. -- adding gaps between widgets according to self.autoarrange_gap.
function Panel:postUpdateLayout() 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 if not self.autoarrange_subviews then return end
local gap = self.autoarrange_gap 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) gui.paint_frame(dc, rect, self.frame_style, self.frame_title)
if self.kbd_get_pos then if self.kbd_get_pos then
local pos = self.kbd_get_pos() 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)) dc:seek(pos.x, pos.y):pen(pen):char(string.char(0xDB))
end end
if self.drag_offset and not self.kbd_get_pos 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)) dc:string((' '):rep(dc.clip_x2 - dc.x))
end 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) function EditField:onInput(keys)
if not self.focus then if not self.focus then
-- only react to our hotkey -- only react to our hotkey
@ -676,22 +685,20 @@ function EditField:onInput(keys)
return true return true
end end
if keys.SELECT then if keys.SELECT or keys.CUSTOM_SHIFT_ENTER then
if self.key then if self.key then
self:setFocus(false) self:setFocus(false)
end end
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 if self.on_submit then
self.on_submit(self.text) self.on_submit(self.text)
return true return true
end 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
end end
return not not self.key return not not self.key
elseif keys._MOUSE_L then elseif keys._MOUSE_L then
@ -712,8 +719,7 @@ function EditField:onInput(keys)
else else
local cv = string.char(keys._STRING) local cv = string.char(keys._STRING)
if not self.on_char or self.on_char(cv, old) then 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:insert(cv)
self.cursor + 1)
elseif self.on_char then elseif self.on_char then
return self.modal return self.modal
end end
@ -722,25 +728,25 @@ function EditField:onInput(keys)
self.on_change(self.text, old) self.on_change(self.text, old)
end end
return true return true
elseif keys.CURSOR_LEFT then elseif keys.KEYBOARD_CURSOR_LEFT then
self:setCursor(self.cursor - 1) self:setCursor(self.cursor - 1)
return true 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): local _, prev_word_end = self.text:sub(1, self.cursor-1):
find('.*[%w_%-][^%w_%-]') find('.*[%w_%-][^%w_%-]')
self:setCursor(prev_word_end or 1) self:setCursor(prev_word_end or 1)
return true return true
elseif keys.A_CARE_MOVE_W then -- Alt-Left (home) elseif keys.CUSTOM_CTRL_A then -- home
self:setCursor(1) self:setCursor(1)
return true return true
elseif keys.CURSOR_RIGHT then elseif keys.KEYBOARD_CURSOR_RIGHT then
self:setCursor(self.cursor + 1) self:setCursor(self.cursor + 1)
return true 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) local _,next_word_start = self.text:find('[^%w_%-][%w_%-]', self.cursor)
self:setCursor(next_word_start) self:setCursor(next_word_start)
return true return true
elseif keys.A_CARE_MOVE_E then -- Alt-Right (end) elseif keys.CUSTOM_CTRL_E then -- end
self:setCursor() self:setCursor()
return true return true
end end
@ -759,14 +765,12 @@ SCROLL_DELAY_MS = 20
Scrollbar = defclass(Scrollbar, Widget) Scrollbar = defclass(Scrollbar, Widget)
Scrollbar.ATTRS{ Scrollbar.ATTRS{
fg = COLOR_LIGHTGREEN,
bg = COLOR_CYAN,
on_scroll = DEFAULT_NIL, on_scroll = DEFAULT_NIL,
} }
function Scrollbar:preinit(init_table) function Scrollbar:preinit(init_table)
init_table.frame = init_table.frame or {} 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 end
function Scrollbar:init() function Scrollbar:init()
@ -824,36 +828,95 @@ local function scrollbar_is_visible(scrollbar)
return scrollbar.elems_per_page < scrollbar.num_elems return scrollbar.elems_per_page < scrollbar.num_elems
end end
local UP_ARROW_CHAR = string.char(24) local SCROLLBAR_UP_LEFT_PEN = to_pen{tile=922, ch=47, fg=COLOR_CYAN, bg=COLOR_BLACK}
local DOWN_ARROW_CHAR = string.char(25) local SCROLLBAR_UP_RIGHT_PEN = to_pen{tile=923, ch=92, fg=COLOR_CYAN, bg=COLOR_BLACK}
local NO_ARROW_CHAR = string.char(32) local SCROLLBAR_DOWN_LEFT_PEN = to_pen{tile=946, ch=92, fg=COLOR_CYAN, bg=COLOR_BLACK}
local BAR_CHAR = string.char(7) local SCROLLBAR_DOWN_RIGHT_PEN = to_pen{tile=947, ch=47, fg=COLOR_CYAN, bg=COLOR_BLACK}
local BAR_BG_CHAR = string.char(179) 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) function Scrollbar:onRenderBody(dc)
-- don't draw if all elements are visible -- don't draw if all elements are visible
if not scrollbar_is_visible(self) then if not scrollbar_is_visible(self) then
return return
end end
-- render up arrow if we're not at the top -- determine which elements should be highlighted
dc:seek(0, 0):char( local _,hover_y = self:getMousePos()
self.top_elem == 1 and NO_ARROW_CHAR or UP_ARROW_CHAR, self.fg, self.bg) 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 -- render scrollbar body
local starty = self.bar_offset + 1 local starty = self.bar_offset + 1
local endy = self.bar_offset + self.bar_height local endy = self.bar_offset + self.bar_height
local midy = (starty + endy)/2
for y=1,dc.height-2 do for y=1,dc.height-2 do
dc:seek(0, y) dc:seek(0, y)
if y >= starty and y <= endy then 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 else
dc:char(BAR_BG_CHAR, self.bg) 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 end
else
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 end
local last_visible_el = self.top_elem + self.elems_per_page - 1 -- render down arrow
dc:seek(0, dc.height-1):char( dc:seek(0, dc.height-1)
last_visible_el >= self.num_elems and NO_ARROW_CHAR or DOWN_ARROW_CHAR, dc:char(nil, hover_down and SCROLLBAR_DOWN_LEFT_HOVER_PEN or SCROLLBAR_DOWN_LEFT_PEN)
self.fg, self.bg) dc:char(nil, hover_down and SCROLLBAR_DOWN_RIGHT_HOVER_PEN or SCROLLBAR_DOWN_RIGHT_PEN)
if not self.on_scroll then return end if not self.on_scroll then return end
-- manage state for dragging and continuous scrolling -- manage state for dragging and continuous scrolling
if self.is_dragging then if self.is_dragging then
@ -877,10 +940,26 @@ function Scrollbar:onRenderBody(dc)
end end
function Scrollbar:onInput(keys) function Scrollbar:onInput(keys)
if not keys._MOUSE_L_DOWN or not self.on_scroll if not self.on_scroll or not scrollbar_is_visible(self) then
or not scrollbar_is_visible(self) then
return false return false
end 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() local _,y = self:getMousePos()
if not y then return false end if not y then return false end
local scroll_spec = nil local scroll_spec = nil
@ -988,13 +1067,13 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled)
if token.tile then if token.tile then
x = x + 1 x = x + 1
if dc then if dc then
dc:char(nil, token.tile) dc:tile(nil, token.tile)
end end
end end
if token.text or token.key then if token.text or token.key then
local text = ''..(getval(token.text) or '') 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 if dc then
local tpen = getval(token.pen) 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 -- we can't set the text in init() since we may not yet have a frame that we
-- can get wrapping bounds from. -- can get wrapping bounds from.
function WrappedLabel:postComputeFrame() 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 if not wrapped_text then return end
local text = {} local text = {}
for _,line in ipairs(wrapped_text:split(NEWLINE)) do for _,line in ipairs(wrapped_text:split(NEWLINE)) do
@ -1622,12 +1701,14 @@ end
function List:submit() function List:submit()
if self.on_submit and #self.choices > 0 then if self.on_submit and #self.choices > 0 then
self.on_submit(self:getSelected()) self.on_submit(self:getSelected())
return true
end end
end end
function List:submit2() function List:submit2()
if self.on_submit2 and #self.choices > 0 then if self.on_submit2 and #self.choices > 0 then
self.on_submit2(self:getSelected()) self.on_submit2(self:getSelected())
return true
end end
end end
@ -1635,12 +1716,10 @@ function List:onInput(keys)
if self:inputToSubviews(keys) then if self:inputToSubviews(keys) then
return true return true
end end
if self.on_submit and keys.SELECT then if keys.SELECT then
self:submit() return self:submit()
return true elseif keys.CUSTOM_SHIFT_ENTER then
elseif self.on_submit2 and keys.SEC_SELECT then return self:submit2()
self:submit2()
return true
elseif keys._MOUSE_L_DOWN then elseif keys._MOUSE_L_DOWN then
local idx = self:getIdxUnderMouse() local idx = self:getIdxUnderMouse()
if idx then if idx then

@ -76,6 +76,7 @@ void Burrows::clearUnits(df::burrow *burrow)
burrow->units.clear(); burrow->units.clear();
/* TODO: understand how this changes for v50
// Sync ui if active // Sync ui if active
if (ui && ui->main.mode == ui_sidebar_mode::Burrows && if (ui && ui->main.mode == ui_sidebar_mode::Burrows &&
ui->burrows.in_add_units_mode && ui->burrows.sel_id == burrow->id) 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++) for (size_t i = 0; i < sel.size(); i++)
sel[i] = false; sel[i] = false;
} }
*/
} }
bool Burrows::isAssignedUnit(df::burrow *burrow, df::unit *unit) 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); erase_from_vector(burrow->units, unit->id);
} }
/* TODO: understand how this changes for v50
// Sync ui if active // Sync ui if active
if (ui && ui->main.mode == ui_sidebar_mode::Burrows && if (ui && ui->main.mode == ui_sidebar_mode::Burrows &&
ui->burrows.in_add_units_mode && ui->burrows.sel_id == burrow->id) 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) if (idx >= 0)
ui->burrows.sel_units[idx] = enable; ui->burrows.sel_units[idx] = enable;
} }
*/
} }
void Burrows::listBlocks(std::vector<df::map_block*> *pvec, df::burrow *burrow) 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/general_ref.h"
#include "df/global_objects.h" #include "df/global_objects.h"
#include "df/graphic.h" #include "df/graphic.h"
#include "df/graphic_viewportst.h"
#include "df/historical_figure.h" #include "df/historical_figure.h"
#include "df/interfacest.h" #include "df/interfacest.h"
#include "df/item_corpsepiecest.h" #include "df/item_corpsepiecest.h"
@ -142,6 +143,7 @@ static std::map<virtual_identity*, getFocusStringHandler> getFocusStringHandlers
DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
{ {
/* TODO: understand how this changes for v50
using namespace df::enums::ui_sidebar_mode; using namespace df::enums::ui_sidebar_mode;
using df::global::ui_workshop_in_add; using df::global::ui_workshop_in_add;
@ -314,7 +316,6 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
focus += "/List"; focus += "/List";
break; break;
/* TODO: understand how this changes for v50
case Hauling: case Hauling:
if (ui->hauling.in_assign_vehicle) if (ui->hauling.in_assign_vehicle)
{ {
@ -353,11 +354,11 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
focus += "/Select/" + tag; focus += "/Select/" + tag;
} }
break; break;
*/
default: default:
break; break;
} }
*/
} }
/* TODO: understand how this changes for v50 /* 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) 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 namespace ui_sidebar_mode;
using df::global::ui_workshop_in_add; using df::global::ui_workshop_in_add;
using df::global::ui_workshop_job_cursor; using df::global::ui_workshop_job_cursor;
if (!dwarfmode_hotkey(top))
return false;
switch (ui->main.mode) { switch (ui->main.mode) {
case QueryBuilding: case QueryBuilding:
{ {
@ -698,16 +700,18 @@ bool Gui::workshop_job_hotkey(df::viewscreen *top)
default: default:
return false; return false;
} }
*/ return false;
} }
bool Gui::build_selector_hotkey(df::viewscreen *top) bool Gui::build_selector_hotkey(df::viewscreen *top)
{ {
using namespace ui_sidebar_mode;
using df::global::ui_build_selector;
if (!dwarfmode_hotkey(top)) if (!dwarfmode_hotkey(top))
return false; return false;
/* TODO: understand how this changes for v50
using namespace ui_sidebar_mode;
using df::global::ui_build_selector;
switch (ui->main.mode) { switch (ui->main.mode) {
case Build: case Build:
{ {
@ -725,20 +729,23 @@ bool Gui::build_selector_hotkey(df::viewscreen *top)
default: default:
return false; return false;
} }
*/ return false;
} }
bool Gui::view_unit_hotkey(df::viewscreen *top) bool Gui::view_unit_hotkey(df::viewscreen *top)
{ {
using df::global::ui_selected_unit;
if (!dwarfmode_hotkey(top)) if (!dwarfmode_hotkey(top))
return false; return false;
/* TODO: understand how this changes for v50
using df::global::ui_selected_unit;
if (ui->main.mode != ui_sidebar_mode::ViewUnits) if (ui->main.mode != ui_sidebar_mode::ViewUnits)
return false; return false;
if (!ui_selected_unit) // allow missing if (!ui_selected_unit) // allow missing
return false; return false;
return vector_get(world->units.active, *ui_selected_unit) != NULL; return vector_get(world->units.active, *ui_selected_unit) != NULL;
*/ return false;
} }
bool Gui::unit_inventory_hotkey(df::viewscreen *top) 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) df::unit *Gui::getAnyUnit(df::viewscreen *top)
{ {
/* TODO: understand how this changes for v50
using namespace ui_sidebar_mode; using namespace ui_sidebar_mode;
using df::global::ui_look_cursor; using df::global::ui_look_cursor;
using df::global::ui_look_list; 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_assign_units;
using df::global::ui_building_item_cursor; using df::global::ui_building_item_cursor;
/* TODO: understand how this changes for v50
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_unitst, top)) if (VIRTUAL_CAST_VAR(screen, df::viewscreen_unitst, top))
{ {
return screen->unit; return screen->unit;
@ -1107,13 +1114,13 @@ df::unit *Gui::getSelectedUnit(color_ostream &out, bool quiet)
df::item *Gui::getAnyItem(df::viewscreen *top) df::item *Gui::getAnyItem(df::viewscreen *top)
{ {
/* TODO: understand how this changes for v50
using namespace ui_sidebar_mode; using namespace ui_sidebar_mode;
using df::global::ui_look_cursor; using df::global::ui_look_cursor;
using df::global::ui_look_list; using df::global::ui_look_list;
using df::global::ui_unit_view_mode; using df::global::ui_unit_view_mode;
using df::global::ui_building_item_cursor; using df::global::ui_building_item_cursor;
/* TODO: understand how this changes for v50
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_textviewerst, top)) if (VIRTUAL_CAST_VAR(screen, df::viewscreen_textviewerst, top))
{ {
// return the main item if the parent screen is a viewscreen_itemst // 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) df::building *Gui::getAnyBuilding(df::viewscreen *top)
{ {
/* TODO: understand how this changes for v50
using namespace ui_sidebar_mode; using namespace ui_sidebar_mode;
using df::global::ui_look_list; using df::global::ui_look_list;
using df::global::ui_look_cursor; using df::global::ui_look_cursor;
/* TODO: understand how this changes for v50
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_buildinglistst, top)) if (VIRTUAL_CAST_VAR(screen, df::viewscreen_buildinglistst, top))
return vector_get(screen->buildings, screen->cursor); return vector_get(screen->buildings, screen->cursor);
@ -1323,10 +1330,12 @@ df::plant *Gui::getAnyPlant(df::viewscreen *top)
if (!cursor || !ui || !world) if (!cursor || !ui || !world)
return nullptr; return nullptr;
/* TODO: understand how this changes for v50
if (ui->main.mode == ui_sidebar_mode::LookAround) if (ui->main.mode == ui_sidebar_mode::LookAround)
{ {
return Maps::getPlantAtTile(cursor->x, cursor->y, cursor->z); return Maps::getPlantAtTile(cursor->x, cursor->y, cursor->z);
} }
*/
} }
return nullptr; return nullptr;
@ -1918,47 +1927,14 @@ Gui::DwarfmodeDims getDwarfmodeViewDims_default()
{ {
Gui::DwarfmodeDims dims; Gui::DwarfmodeDims dims;
auto ws = Screen::getWindowSize(); bool use_graphics = Screen::inGraphicsMode();
dims.y1 = 1; auto dimx = use_graphics ? gps->main_viewport->dim_x : gps->dimx;
dims.y2 = ws.y-2; auto dimy = use_graphics ? gps->main_viewport->dim_y : gps->dimy;
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);
if (dims.menu_on) dims.map_x1 = 0;
{ dims.map_x2 = dimx - 1;
dims.menu_x2 = ws.x - 2; dims.map_y1 = 0;
dims.menu_x1 = dims.menu_x2 - Gui::MENU_WIDTH + 1; dims.map_y2 = dimy - 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;
}
return dims; return dims;
} }
@ -1977,7 +1953,9 @@ void Gui::resetDwarfmodeView(bool pause)
{ {
ui->follow_unit = -1; ui->follow_unit = -1;
ui->follow_item = -1; ui->follow_item = -1;
/* TODO: understand how this changes for v50
ui->main.mode = ui_sidebar_mode::Default; ui->main.mode = ui_sidebar_mode::Default;
*/
} }
if (selection_rect) if (selection_rect)
@ -2151,16 +2129,31 @@ df::coord Gui::getMousePos()
df::coord pos; df::coord pos;
if (gps && gps->precise_mouse_x > -1) { if (gps && gps->precise_mouse_x > -1) {
pos = getViewportPos(); pos = getViewportPos();
/* TODO: understand how this changes for v50 if (Screen::inGraphicsMode()) {
pos.x += gps->mouse_x_pixel / tile_width; int32_t map_tile_pixels = gps->viewport_zoom_factor / 4;
pos.y += gps->mouse_y_pixel / tile_height; 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; return pos;
} }
int getDepthAt_default (int32_t x, int32_t y) 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; return 0;
} }
@ -2183,17 +2176,3 @@ bool Gui::getWindowSize (int32_t &width, int32_t &height)
return false; 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: case general_ref_type::UNIT_HOLDER:
if (auto unit = ref->getUnit()) if (auto unit = ref->getUnit())
{ {
/* TODO: understand how this changes for v50
// Unit view sidebar holds inventory item pointers // Unit view sidebar holds inventory item pointers
if (ui->main.mode == ui_sidebar_mode::ViewUnits && if (ui->main.mode == ui_sidebar_mode::ViewUnits &&
(!ui_selected_unit || (!ui_selected_unit ||
vector_get(world->units.active, *ui_selected_unit) == unit)) vector_get(world->units.active, *ui_selected_unit) == unit))
return false; return false;
*/
for (int i = unit->inventory.size()-1; i >= 0; i--) 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) { 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; size_t index = (x * gps->main_viewport->dim_y) + y;
if (index < 0 || index > max_index)
return false;
long texpos = pen.tile; long texpos = pen.tile;
if (texpos == 0) { if (texpos == 0) {
texpos = init->font.large_font_texpos[(uint8_t)pen.ch]; 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; *screen = 0;
*texpos = 0; *texpos = 0;
*texpos_lower = 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) { if (gps->top_in_use) {
screen = &gps->screen_top[index * 8]; 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; *screen = 0;
*texpos = 0; *texpos = 0;
*texpos_lower = 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) if (pen.tile_mode == Screen::Pen::CharColor)
*flag |= 2; // SCREENTEXPOS_FLAG_ADDCOLOR *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 *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) { if (pen.tile && use_graphics) {
*texpos = pen.tile; *texpos = pen.tile;
@ -171,15 +188,14 @@ static bool doSetTile_default(const Pen &pen, int x, int y, bool map)
*texpos_lower = 909; *texpos_lower = 909;
} }
// note that pen.bold currently (50.04) has no representation in the DF data auto rgb_fg = &gps->uccolor[fg][0];
auto fg = &gps->uccolor[pen.fg][0]; auto rgb_bg = &gps->uccolor[bg][0];
auto bg = &gps->uccolor[pen.bg][0]; screen[1] = rgb_fg[0];
screen[1] = fg[0]; screen[2] = rgb_fg[1];
screen[2] = fg[1]; screen[3] = rgb_fg[2];
screen[3] = fg[2]; screen[4] = rgb_bg[0];
screen[4] = bg[0]; screen[5] = rgb_bg[1];
screen[5] = bg[1]; screen[6] = rgb_bg[2];
screen[6] = bg[2];
return true; return true;
} }
@ -198,39 +214,83 @@ bool Screen::paintTile(const Pen &pen, int x, int y, bool map)
return true; return true;
} }
static Pen doGetTile_default(int x, int y, bool map) static Pen doGetTile_map(int x, int y) {
{ size_t max_index = gps->main_viewport->dim_x * gps->main_viewport->dim_y - 1;
auto dim = Screen::getWindowSize(); size_t index = (x * gps->main_viewport->dim_y) + y;
if (x < 0 || x >= dim.x || y < 0 || y >= dim.y)
return Pen(0,0,0,-1);
/* TODO: understand how this changes for v50 if (index < 0 || index > max_index)
int index = x*dim.y + y;
auto screen = gps->screen + index*4;
if (screen[3] & 0x80)
return Pen(0, 0, 0, -1); return Pen(0, 0, 0, -1);
Pen pen( int tile = gps->main_viewport->screentexpos[index];
screen[0], screen[1], screen[2], screen[3]?true:false, if (tile == 0)
gps->screentexpos[index] 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];
if (pen.tile) char ch = 0;
{ uint8_t fg = 0;
if (gps->screentexpos_grayscale[index]) uint8_t bg = 0;
{ return Pen(ch, fg, bg, tile, false);
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])
{ static uint8_t to_16_bit_color(uint8_t *rgb) {
pen.tile_mode = Screen::Pen::CharColor; 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;
}
static Pen doGetTile_default(int x, int y, bool map) {
if (x < 0 || y < 0)
return Pen(0, 0, 0, -1);
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; // AsIs
*/ return Pen(0,0,0,-1); return Pen(ch, fg, bg, tile, false);
} }
GUI_HOOK_DEFINE(Screen::Hooks::get_tile, doGetTile_default); 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(3dveins 3dveins.cpp)
#dfhack_plugin(add-spatter add-spatter.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(autochop autochop.cpp)
#dfhack_plugin(autoclothing autoclothing.cpp) #dfhack_plugin(autoclothing autoclothing.cpp)
#dfhack_plugin(autodump autodump.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(nestboxes nestboxes.cpp)
#dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static) #dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static)
dfhack_plugin(overlay overlay.cpp LINK_LIBRARIES lua) 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(petcapRemover petcapRemover.cpp)
#dfhack_plugin(plants plants.cpp) #dfhack_plugin(plants plants.cpp)
#dfhack_plugin(probe probe.cpp) #dfhack_plugin(probe probe.cpp)

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

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

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

@ -1,74 +1,88 @@
#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/Gui.h"
#include "modules/Maps.h" #include "modules/Maps.h"
#include "modules/Screen.h" #include "modules/Screen.h"
#include "df/world.h"
#include "Debug.h"
#include "LuaTools.h"
#include "PluginManager.h"
using namespace DFHack; using namespace DFHack;
DFHACK_PLUGIN("pathable"); DFHACK_PLUGIN("pathable");
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(window_x); REQUIRE_GLOBAL(window_x);
REQUIRE_GLOBAL(window_y); REQUIRE_GLOBAL(window_y);
REQUIRE_GLOBAL(window_z); REQUIRE_GLOBAL(window_z);
REQUIRE_GLOBAL(world);
namespace DFHack {
DBG_DECLARE(pathable, log, DebugCategory::LINFO);
}
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) {
{
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_shutdown(color_ostream &out) DFhackCExport command_result plugin_shutdown(color_ostream &out) {
{
return CR_OK; return CR_OK;
} }
static void paintScreen(df::coord cursor, bool skip_unrevealed = false) static void paintScreen(df::coord target, bool skip_unrevealed = false) {
{ DEBUG(log).print("entering paintScreen\n");
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;
df::coord map_pos( bool use_graphics = Screen::inGraphicsMode();
*window_x + x - dims.map_x1,
*window_y + y - dims.map_y1,
*window_z
);
// Keep yellow cursor int selected_tile_texpos = 0;
if (map_pos == cursor) Screen::findGraphicsTile("CURSORS", 4, 3, &selected_tile_texpos);
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);
if (!Maps::isValidTilePos(map_pos))
continue; continue;
if (map_pos.x < 0 || map_pos.x >= world->map.x_count || // don't overwrite the target tile
map_pos.y < 0 || map_pos.y >= world->map.y_count || if (!use_graphics && map_pos == target) {
map_pos.z < 0 || map_pos.z >= world->map.z_count) TRACE(log).print("skipping target tile\n");
{
continue; continue;
} }
if (skip_unrevealed && !Maps::isTileVisible(map_pos)) if (skip_unrevealed && !Maps::isTileVisible(map_pos)) {
TRACE(log).print("skipping hidden tile\n");
continue; 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 != ' ') if (!cur_tile.valid()) {
{ DEBUG(log).print("cannot read tile at offset %d, %d\n", x, y);
continue;
}
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.fg = color;
cur_tile.bg = 0; cur_tile.bg = 0;
} } else {
else
{
cur_tile.fg = 0; cur_tile.fg = 0;
cur_tile.bg = color; cur_tile.bg = color;
} }
@ -77,6 +91,7 @@ static void paintScreen(df::coord cursor, bool skip_unrevealed = false)
if (cur_tile.tile) if (cur_tile.tile)
cur_tile.tile_mode = Screen::Pen::CharColor; cur_tile.tile_mode = Screen::Pen::CharColor;
}
Screen::paintTile(cur_tile, x, y, true); Screen::paintTile(cur_tile, x, y, true);
} }

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