Merge branch 'develop' into reenable-spectatte

develop
Myk 2023-10-03 10:50:24 -07:00 committed by GitHub
commit 726828af2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1050 additions and 492 deletions

@ -12,45 +12,45 @@ This release is compatible with all distributions of Dwarf Fortress: [Steam](htt
Please report any issues (or feature requests) on the DFHack [GitHub issue tracker](https://github.com/DFHack/dfhack/issues). When reporting issues, please upload a zip file of your savegame and a zip file of your `mods` directory to the cloud and add links to the GitHub issue. Make sure your files are downloadable by "everyone with the link". We need your savegame to reproduce the problem and test the fix, and we need your active mods so we can load your savegame. Issues with savegames and mods attached get fixed first! Please report any issues (or feature requests) on the DFHack [GitHub issue tracker](https://github.com/DFHack/dfhack/issues). When reporting issues, please upload a zip file of your savegame and a zip file of your `mods` directory to the cloud and add links to the GitHub issue. Make sure your files are downloadable by "everyone with the link". We need your savegame to reproduce the problem and test the fix, and we need your active mods so we can load your savegame. Issues with savegames and mods attached get fixed first!
Announcements Highlights
---------------------------------- ----------------------------------
<details> <details>
<summary>Annc 1, PSAs</summary> <summary>Highlight 1, Highlight 2</summary>
### Annc 1
Text ### Highlight 1
### PSAs Demo screenshot/vidcap
As always, remember that, just like the vanilla DF game, DFHack tools can also have bugs. It is a good idea to **save often and keep backups** of the forts that you care about. Text
Many DFHack tools that worked in previous (pre-Steam) versions of DF have not been updated yet and are marked with the "unavailable" tag in their docs. If you try to run them, they will show a warning and exit immediately. You can run the command again to override the warning (though of course the tools may not work). We make no guarantees of reliability for the tools that are marked as "unavailable". ### Highlight 2
The in-game interface for running DFHack commands (`gui/launcher`) will not show "unavailable" tools by default. You can still run them if you know their names, or you can turn on dev mode by hitting Ctrl-D while in `gui/launcher` and they will be added to the autocomplete list. Some tools do not compile yet and are not available at all, even when in dev mode. Demo screenshot/vidcap
If you see a tool complaining about the lack of a cursor, know that it's referring to the **keyboard** cursor (which used to be the only real option in Dwarf Fortress). You can enable the keyboard cursor by entering mining mode or selecting the dump/forbid tool and hitting Alt-K (the DFHack keybinding for `toggle-kbd-cursor`. We're working on making DFHack tools more mouse-aware and accessible so this step isn't necessary in the future. Text
</details> </details>
Highlights Announcements
---------------------------------- ----------------------------------
<details> <details>
<summary>Highlight 1, Highlight 2</summary> <summary>Annc 1, PSAs</summary>
### Highlight 1
Demo screenshot/vidcap ### Annc 1
Text Text
### Highlight 2 ### PSAs
Demo screenshot/vidcap As always, remember that, just like the vanilla DF game, DFHack tools can also have bugs. It is a good idea to **save often and keep backups** of the forts that you care about.
Text Many DFHack tools that worked in previous (pre-Steam) versions of DF have not been updated yet and are marked with the "unavailable" tag in their docs. If you try to run them, they will show a warning and exit immediately. You can run the command again to override the warning (though of course the tools may not work). We make no guarantees of reliability for the tools that are marked as "unavailable".
The in-game interface for running DFHack commands (`gui/launcher`) will not show "unavailable" tools by default. You can still run them if you know their names, or you can turn on dev mode by hitting Ctrl-D while in `gui/launcher` and they will be added to the autocomplete list. Some tools do not compile yet and are not available at all, even when in dev mode.
If you see a tool complaining about the lack of a cursor, know that it's referring to the **keyboard** cursor (which used to be the only real option in Dwarf Fortress). You can enable the keyboard cursor by entering mining mode or selecting the dump/forbid tool and hitting Alt-K (the DFHack keybinding for `toggle-kbd-cursor`. We're working on making DFHack tools more mouse-aware and accessible so this step isn't necessary in the future.
</details> </details>
@ -61,5 +61,4 @@ Generated release notes
<summary>New tools, fixes, and improvements</summary> <summary>New tools, fixes, and improvements</summary>
%RELEASE_NOTES% %RELEASE_NOTES%
</details> </details>

@ -67,19 +67,25 @@ jobs:
compiler: msvc compiler: msvc
plugins: "default" plugins: "default"
config: "empty" config: "empty"
# TODO: uncomment once we have a linux build we can download from bay12 - os: ubuntu
# - os: ubuntu compiler: gcc-10
# compiler: gcc-10 plugins: "default"
# plugins: "default" config: "default"
# config: "default" - os: ubuntu
# - os: ubuntu compiler: gcc-12
# compiler: gcc-12 plugins: "all"
# plugins: "all" config: "default"
# config: "default"
steps: steps:
- name: Set env - name: Set env
shell: bash shell: bash
run: echo "DF_FOLDER=DF" >> $GITHUB_ENV run: echo "DF_FOLDER=DF" >> $GITHUB_ENV
- name: Install dependencies
if: matrix.os == 'ubuntu'
run: |
sudo apt-get update
sudo apt-get install \
libsdl2-2.0-0 \
libsdl2-image-2.0-0
- name: Clone DFHack - name: Clone DFHack
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
@ -117,8 +123,14 @@ jobs:
- name: Install DFHack - name: Install DFHack
shell: bash shell: bash
run: tar xjf test-${{ matrix.compiler }}.tar.bz2 -C ${{ env.DF_FOLDER }} run: tar xjf test-${{ matrix.compiler }}.tar.bz2 -C ${{ env.DF_FOLDER }}
- name: Start X server
if: matrix.os == 'ubuntu'
run: Xvfb :0 -screen 0 1600x1200x24 &
- name: Run lua tests - name: Run lua tests
timeout-minutes: 10 timeout-minutes: 10
env:
DISPLAY: :0
TERM: xterm-256color
run: python ci/run-tests.py --keep-status "${{ env.DF_FOLDER }}" run: python ci/run-tests.py --keep-status "${{ env.DF_FOLDER }}"
- name: Check RPC interface - name: Check RPC interface
run: python ci/check-rpc.py "${{ env.DF_FOLDER }}/dfhack-rpc.txt" run: python ci/check-rpc.py "${{ env.DF_FOLDER }}/dfhack-rpc.txt"

@ -20,7 +20,7 @@ repos:
args: ['--fix=lf'] args: ['--fix=lf']
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/python-jsonschema/check-jsonschema - repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.26.3 rev: 0.27.0
hooks: hooks:
- id: check-github-workflows - id: check-github-workflows
- repo: https://github.com/Lucas-C/pre-commit-hooks - repo: https://github.com/Lucas-C/pre-commit-hooks

@ -7,7 +7,7 @@ cmake_policy(SET CMP0074 NEW)
project(dfhack) project(dfhack)
# set up versioning. # set up versioning.
set(DF_VERSION "50.10") set(DF_VERSION "50.11")
set(DFHACK_RELEASE "r1") set(DFHACK_RELEASE "r1")
set(DFHACK_PRERELEASE FALSE) set(DFHACK_PRERELEASE FALSE)

@ -18,7 +18,7 @@ elif test "$OS_TARGET" = "ubuntu"; then
WGET=wget WGET=wget
df_url="${df_url}_linux.tar.bz2" df_url="${df_url}_linux.tar.bz2"
df_archive_name="df.tar.bz2" df_archive_name="df.tar.bz2"
df_extract_cmd="tar -x -j --strip-components=1 -f" df_extract_cmd="tar -x -j -C ${DF_FOLDER} -f"
else else
echo "Unhandled OS target: ${OS_TARGET}" echo "Unhandled OS target: ${OS_TARGET}"
exit 1 exit 1
@ -29,22 +29,25 @@ if ! $WGET -v "$df_url" -O "$df_archive_name"; then
exit 1 exit 1
fi fi
md5sum "$df_archive_name"
save_url="https://dffd.bay12games.com/download.php?id=15434&f=dreamfort.7z" save_url="https://dffd.bay12games.com/download.php?id=15434&f=dreamfort.7z"
save_archive_name="test_save.7z" save_archive_name="test_save.7z"
save_extract_cmd="7z x -oDF/save" save_extract_cmd="7z x -o${DF_FOLDER}/save"
if ! $WGET -v "$save_url" -O "$save_archive_name"; then if ! $WGET -v "$save_url" -O "$save_archive_name"; then
echo "Failed to download test save from $save_url" echo "Failed to download test save from $save_url"
exit 1 exit 1
fi fi
md5sum "$save_archive_name"
echo Extracting echo Extracting
mkdir -p ${DF_FOLDER}
$df_extract_cmd "$df_archive_name" $df_extract_cmd "$df_archive_name"
$save_extract_cmd "$save_archive_name" $save_extract_cmd "$save_archive_name"
mv DF/save/* DF/save/region1 mv ${DF_FOLDER}/save/* ${DF_FOLDER}/save/region1
echo Done echo Done
ls -l ls -l
md5sum "$df_archive_name" "$save_archive_name"

@ -65,14 +65,12 @@ if not os.path.exists(init_txt_path):
shutil.copyfile(init_txt_path, init_txt_path + '.orig') shutil.copyfile(init_txt_path, init_txt_path + '.orig')
with open(init_txt_path) as f: with open(init_txt_path) as f:
init_contents = f.read() init_contents = f.read()
init_contents = change_setting(init_contents, 'INTRO', 'NO')
init_contents = change_setting(init_contents, 'SOUND', 'NO') init_contents = change_setting(init_contents, 'SOUND', 'NO')
init_contents = change_setting(init_contents, 'WINDOWED', 'YES') init_contents = change_setting(init_contents, 'WINDOWED', 'YES')
init_contents = change_setting(init_contents, 'WINDOWEDX', '80') init_contents = change_setting(init_contents, 'WINDOWEDX', '1200')
init_contents = change_setting(init_contents, 'WINDOWEDY', '25') init_contents = change_setting(init_contents, 'WINDOWEDY', '800')
init_contents = change_setting(init_contents, 'FPS', 'YES') #if args.headless:
if args.headless: # init_contents = change_setting(init_contents, 'PRINT_MODE', 'TEXT')
init_contents = change_setting(init_contents, 'PRINT_MODE', 'TEXT')
init_path = 'dfhack-config/init' init_path = 'dfhack-config/init'
if not os.path.isdir('hack/init'): if not os.path.isdir('hack/init'):

@ -2,6 +2,7 @@
--@ module = true --@ module = true
local expect = require('test_util.expect') local expect = require('test_util.expect')
local gui = require('gui')
local helpdb = require('helpdb') local helpdb = require('helpdb')
local json = require('json') local json = require('json')
local mock = require('test_util.mock') local mock = require('test_util.mock')
@ -151,33 +152,37 @@ end
test_envvars.require = clean_require test_envvars.require = clean_require
test_envvars.reqscript = clean_reqscript test_envvars.reqscript = clean_reqscript
local function is_title_screen(scr) local function is_title_screen()
scr = scr or dfhack.gui.getCurViewscreen() return dfhack.gui.matchFocusString('title/Default')
return df.viewscreen_titlest:is_instance(scr)
end end
-- This only handles pre-fortress-load screens. It will time out if the player local function wait_for(ms, desc, predicate)
-- has already loaded a fortress or is in any screen that can't get to the title
-- screen by sending ESC keys.
local function ensure_title_screen()
local start_ms = dfhack.getTickCount() local start_ms = dfhack.getTickCount()
local prev_ms = start_ms local prev_ms = start_ms
while df.viewscreen_initial_prepst:is_instance(dfhack.gui.getCurViewscreen()) do while not predicate() do
delay(10) delay(10)
-- wait up to 1 minute for the game to load and show the title screen
local now_ms = dfhack.getTickCount() local now_ms = dfhack.getTickCount()
if now_ms - start_ms > 60000 then if now_ms - start_ms > ms then
qerror(('Could not find title screen (timed out at %s)'):format( qerror(('%s took too long (timed out at %s)'):format(
dfhack.gui.getCurFocus(true)[1])) desc, dfhack.gui.getCurFocus(true)[1]))
end end
if now_ms - prev_ms > 1000 then if now_ms - prev_ms > 1000 then
print('Waiting for game to load and show title screen...') print(('Waiting for %s...'):format(desc))
prev_ms = now_ms prev_ms = now_ms
end end
end end
end
-- This only handles pre-fortress-load screens. It will time out if the player
-- has already loaded a fortress or is in any screen that can't get to the title
-- screen by sending ESC keys.
local function ensure_title_screen()
if df.viewscreen_dwarfmodest:is_instance(dfhack.gui.getDFViewscreen(true)) then
qerror('Cannot reach title screen from loaded fort')
end
for i = 1, 100 do for i = 1, 100 do
local scr = dfhack.gui.getCurViewscreen() local scr = dfhack.gui.getCurViewscreen()
if is_title_screen(scr) then if is_title_screen() then
print('Found title screen') print('Found title screen')
return return
end end
@ -189,54 +194,91 @@ local function ensure_title_screen()
dfhack.gui.getCurFocus(true)[1])) dfhack.gui.getCurFocus(true)[1]))
end end
local function is_fortress(focus_string) local function is_fortress()
focus_string = focus_string or dfhack.gui.getCurFocus(true) return dfhack.gui.matchFocusString('dwarfmode/Default')
return focus_string == 'dwarfmode/Default' end
-- error out if we're not running in a CI environment
-- the tests may corrupt saves, and we don't want to unexpectedly ruin a real player save
-- this heuristic is not perfect, but it should be able to detect most cases
local function ensure_ci_save(scr)
if #scr.savegame_header ~= 1
or #scr.savegame_header_world ~= 1
or not string.find(scr.savegame_header[0].fort_name, 'Dream')
then
qerror('Unexpected test save in slot 0; please manually load a fort for ' ..
'running fortress mode tests. note that tests may alter or corrupt the ' ..
'fort! Do not save after running tests.')
end
end
local function click_top_title_button(scr)
local sw, sh = dfhack.screen.getWindowSize()
df.global.gps.mouse_x = sw // 2
df.global.gps.precise_mouse_x = df.global.gps.mouse_x * df.global.gps.tile_pixel_x
if sh < 60 then
df.global.gps.mouse_y = 25
else
df.global.gps.mouse_y = (sh // 2) + 3
end
df.global.gps.precise_mouse_y = df.global.gps.mouse_y * df.global.gps.tile_pixel_y
gui.simulateInput(scr, '_MOUSE_L')
end
local function load_first_save(scr)
if #scr.savegame_header == 0 then
qerror('no savegames available to load')
end
click_top_title_button(scr)
wait_for(1000, 'world list', function()
return scr.mode == 2
end)
click_top_title_button(scr)
wait_for(1000, 'savegame list', function()
return scr.mode == 3
end)
click_top_title_button(scr)
wait_for(1000, 'loadgame progress bar', function()
return dfhack.gui.matchFocusString('loadgame')
end)
end end
-- Requires that a fortress game is already loaded or is ready to be loaded via -- Requires that a fortress game is already loaded or is ready to be loaded via
-- the "Continue Playing" option in the title screen. Otherwise the function -- the "Continue active game" option in the title screen. Otherwise the function
-- will time out and/or exit with error. -- will time out and/or exit with error.
local function ensure_fortress(config) local function ensure_fortress(config)
local focus_string = dfhack.gui.getCurFocus(true)
for screen_timeout = 1,10 do for screen_timeout = 1,10 do
if is_fortress(focus_string) then if is_fortress() then
print('Loaded fortress map') print('Fortress map is loaded')
-- pause the game (if it's not already paused) -- pause the game (if it's not already paused)
dfhack.gui.resetDwarfmodeView(true) dfhack.gui.resetDwarfmodeView(true)
return return
end end
local scr = dfhack.gui.getCurViewscreen(true) local scr = dfhack.gui.getCurViewscreen()
if focus_string == 'title' or if dfhack.gui.matchFocusString('title/Default', scr) then
focus_string == 'dfhack/lua/load_screen' then print('Attempting to load the test fortress')
-- TODO: reinstate loading of a specified save dir; for now
-- just load the first possible save, which will at least let us
-- run fortress tests in CI
-- qerror()'s on falure -- qerror()'s on falure
dfhack.run_script('load-save', config.save_dir) -- dfhack.run_script('load-save', config.save_dir)
elseif focus_string ~= 'loadgame' then ensure_ci_save(scr)
load_first_save(scr)
elseif not dfhack.gui.matchFocusString('loadgame', scr) then
-- if we're not actively loading a game, hope we're in -- if we're not actively loading a game, hope we're in
-- a screen where hitting ESC will get us to the game map -- a screen where hitting ESC will get us to the game map
-- or the title screen -- or the title screen
scr:feed_key(df.interface_key.LEAVESCREEN) scr:feed_key(df.interface_key.LEAVESCREEN)
end end
-- wait for current screen to change -- wait for current screen to change
local prev_focus_string = focus_string local prev_focus_string = dfhack.gui.getCurFocus()[1]
for frame_timeout = 1,100 do wait_for(60000, 'screen change', function()
delay(10) return dfhack.gui.getCurFocus()[1] ~= prev_focus_string
focus_string = dfhack.gui.getCurFocus(true) end)
if focus_string ~= prev_focus_string then
goto next_screen
end
if frame_timeout % 10 == 0 then
print(string.format(
'Loading fortress (currently at screen: %s)',
focus_string))
end
end
print('Timed out waiting for screen to change')
break
::next_screen::
end end
qerror(string.format('Could not load fortress (timed out at %s)', qerror(string.format('Could not load fortress (timed out at %s)',
focus_string)) table.concat(dfhack.gui.getCurFocus(), ' ')))
end end
local MODES = { local MODES = {
@ -578,6 +620,10 @@ local function filter_tests(tests, config)
end end
local function run_tests(tests, status, counts, config) local function run_tests(tests, status, counts, config)
wait_for(60000, 'game load', function()
local scr = dfhack.gui.getDFViewscreen()
return not df.viewscreen_initial_prepst:is_instance(scr)
end)
print(('Running %d tests'):format(#tests)) print(('Running %d tests'):format(#tests))
local start_ms = dfhack.getTickCount() local start_ms = dfhack.getTickCount()
local num_skipped = 0 local num_skipped = 0
@ -596,12 +642,13 @@ local function run_tests(tests, status, counts, config)
goto skip goto skip
end end
end end
-- pre-emptively mark the test as failed in case we induce a crash
status[test.full_name] = TestStatus.FAILED
save_test_status(status)
if run_test(test, status, counts) then if run_test(test, status, counts) then
status[test.full_name] = TestStatus.PASSED status[test.full_name] = TestStatus.PASSED
else
status[test.full_name] = TestStatus.FAILED
end
save_test_status(status) save_test_status(status)
end
::skip:: ::skip::
end end
local elapsed_ms = dfhack.getTickCount() - start_ms local elapsed_ms = dfhack.getTickCount() - start_ms

@ -139,6 +139,7 @@ moversti moversti
mrrho mrrho mrrho mrrho
Murad Beybalaev Erquint Murad Beybalaev Erquint
Myk Taylor myk002 Myk Taylor myk002
Najeeb Al-Shabibi master-spike
napagokc napagokc napagokc napagokc
Neil Little nmlittle Neil Little nmlittle
Nick Rart nickrart comestible Nick Rart nickrart comestible

@ -10,6 +10,13 @@ work (e.g. links from the `changelog`).
:local: :local:
:depth: 1 :depth: 1
.. _workorder-recheck:
workorder-recheck
=================
Tool to set 'Checking' status of the selected work order, allowing conditions to be
reevaluated. Merged into `orders`.
.. _autohauler: .. _autohauler:
autohauler autohauler

@ -68,12 +68,36 @@ Template for new versions:
## Removed ## Removed
# 50.11-r1
## New Tools
- `tubefill`: (reinstated) replenishes mined-out adamantine
## Fixes
- `autolabor`: ensure vanilla work details are reinstated when the fort or the plugin is unloaded
- ``dfhack.TranslateName()``: fixed crash on certain invalid names, which affected `warn-starving`
- EventManager: Unit death event no longer misfires on units leaving the map
## Misc Improvements
- `digtype`: designate only visible tiles by default, and use "auto" dig mode for following veins
- `digtype`: added options for designating only current z-level, this z-level and above, and this z-level and below
- `hotkeys`: make the DFHack logo brighten on hover in ascii mode to indicate that it is clickable
- `hotkeys`: use vertical bars instead of "!" symbols for the DFHack logo in ascii mode to make it easier to read
- EventManager: guard against potential iterator invalidation if one of the event listeners were to modify the global data structure being iterated over
- EventManager: for ``onBuildingCreatedDestroyed`` events, changed firing order of events so destroyed events come before created events
## Lua
- mouse key events are now aligned with internal DF semantics: ``_MOUSE_L`` indicates that the left mouse button has just been pressed and ``_MOUSE_L_DOWN`` indicates that the left mouse button is being held down. similarly for ``_MOUSE_R`` and ``_MOUSE_M``. 3rd party scripts may have to adjust.
# 50.10-r1 # 50.10-r1
## Fixes ## Fixes
- Linux launcher: allow Steam Overlay and game streaming to function - Linux launcher: allow Steam Overlay and game streaming to function
- `autobutcher`: don't ignore semi-wild units when marking units for slaughter - `autobutcher`: don't ignore semi-wild units when marking units for slaughter
## Misc Improvements
- 'sort': Improve combat skill scale thresholds
# 50.09-r4 # 50.09-r4
## New Features ## New Features

@ -2528,10 +2528,10 @@ Supported callbacks and fields are:
Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience. Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience.
``_MOUSE_L, _MOUSE_R, _MOUSE_M`` ``_MOUSE_L, _MOUSE_R, _MOUSE_M``
If the left, right, and/or middle mouse button is being pressed. If the left, right, and/or middle mouse button was just pressed.
``_MOUSE_L_DOWN, _MOUSE_R_DOWN, _MOUSE_M_DOWN`` ``_MOUSE_L_DOWN, _MOUSE_R_DOWN, _MOUSE_M_DOWN``
If the left, right, and/or middle mouse button was just pressed. If the left, right, and/or middle mouse button is being held down.
If this method is omitted, the screen is dismissed on reception of the ``LEAVESCREEN`` key. If this method is omitted, the screen is dismissed on reception of the ``LEAVESCREEN`` key.
@ -2594,11 +2594,18 @@ invalidates the ``texpos`` value that used to point to that texture.
The ``textures`` module solves this problem by providing a stable handle instead of a The ``textures`` module solves this problem by providing a stable handle instead of a
raw ``texpos``. When we need to draw a particular tile, we can look up the current raw ``texpos``. When we need to draw a particular tile, we can look up the current
``texpos`` value via the handle. ``texpos`` value via the handle.
Texture module can register textures in two ways: to reserved and dynamic ranges.
Reserved range is a limit buffer in a game texture vector, that will never be wiped.
It is good for static assets, which need to be loaded at the very beginning and will be used during the process running.
In other cases, it is better to use dynamic range.
If reserved range buffer limit has been reached, dynamic range will be used by default.
* ``loadTileset(file, tile_px_w, tile_px_h)`` * ``loadTileset(file, tile_px_w, tile_px_h[, reserved])``
Loads a tileset from the image ``file`` with give tile dimensions in pixels. The Loads a tileset from the image ``file`` with give tile dimensions in pixels. The
image will be sliced in row major order. Returns an array of ``TexposHandle``. image will be sliced in row major order. Returns an array of ``TexposHandle``.
``reserved`` is optional boolean argument, which indicates texpos range.
``true`` - reserved, ``false`` - dynamic (default).
Example usage:: Example usage::
@ -2611,18 +2618,22 @@ raw ``texpos``. When we need to draw a particular tile, we can look up the curre
get the ``texpos`` for your texture. ``texpos`` can change when game textures are get the ``texpos`` for your texture. ``texpos`` can change when game textures are
reset, but the handle will be the same. reset, but the handle will be the same.
* ``createTile(pixels, tile_px_w, tile_px_h)`` * ``createTile(pixels, tile_px_w, tile_px_h[, reserved])``
Create and register a new texture with the given tile dimensions and an array of Create and register a new texture with the given tile dimensions and an array of
``pixels`` in row major order. Each pixel is an integer representing color in packed ``pixels`` in row major order. Each pixel is an integer representing color in packed
RBGA format (for example, #0022FF11). Returns a ``TexposHandle``. RBGA format (for example, #0022FF11). Returns a ``TexposHandle``.
``reserved`` is optional boolean argument, which indicates texpos range.
``true`` - reserved, ``false`` - dynamic (default).
* ``createTileset(pixels, texture_px_w, texture_px_h, tile_px_w, tile_px_h)`` * ``createTileset(pixels, texture_px_w, texture_px_h, tile_px_w, tile_px_h[, reserved])``
Create and register a new texture with the given texture dimensions and an array of Create and register a new texture with the given texture dimensions and an array of
``pixels`` in row major order. Then slice it into tiles with the given tile ``pixels`` in row major order. Then slice it into tiles with the given tile
dimensions. Each pixel is an integer representing color in packed RBGA format (for dimensions. Each pixel is an integer representing color in packed RBGA format (for
example #0022FF11). Returns an array of ``TexposHandle``. example #0022FF11). Returns an array of ``TexposHandle``.
``reserved`` is optional boolean argument, which indicates texpos range.
``true`` - reserved, ``false`` - dynamic (default).
* ``deleteHandle(handle)`` * ``deleteHandle(handle)``
@ -3941,6 +3952,14 @@ Misc
of keycodes to *true* or *false*. For instance, it is possible to use the of keycodes to *true* or *false*. For instance, it is possible to use the
table passed as argument to ``onInput``. table passed as argument to ``onInput``.
You can send mouse clicks as will by setting the ``_MOUSE_L`` key or other
mouse-related pseudo-keys documented with the ``screen:onInput(keys)``
function above. Note that if you are simulating a click at a specific spot on
the screen, you must set ``df.global.gps.mouse_x`` and
``df.global.gps.mouse_y`` if you are clicking on the interface layer or
``df.global.gps.precise_mouse_x`` and ``df.global.gps.precise_mouse_y`` if
you are clicking on the map.
* ``mkdims_xy(x1,y1,x2,y2)`` * ``mkdims_xy(x1,y1,x2,y2)``
Returns a table containing the arguments as fields, and also ``width`` and Returns a table containing the arguments as fields, and also ``width`` and

@ -50,7 +50,7 @@ Usage
Designate circles. The diameter is the number of tiles across the center of Designate circles. The diameter is the number of tiles across the center of
the circle that you want to dig. See the `digcircle`_ section below for the circle that you want to dig. See the `digcircle`_ section below for
options. options.
``digtype [<designation>] [-p<number>] [-z]`` ``digtype [<designation>] [-p<number>] [--zup|-u] [--zdown|-zu] [--cur-zlevel|-z] [--hidden|-h] [--no-auto|-a]``
Designate all vein tiles of the same type as the selected tile. See the Designate all vein tiles of the same type as the selected tile. See the
`digtype`_ section below for options. `digtype`_ section below for options.
``digexp [<pattern>] [<filter>] [-p<number>]`` ``digexp [<pattern>] [<filter>] [-p<number>]``
@ -119,9 +119,11 @@ the last selected parameters.
digtype digtype
------- -------
For every tile on the map of the same vein type as the selected tile, this For every tile on the map of the same vein type as the selected tile, this command
command designates it to have the same designation as the selected tile. If the designates it to have the same designation as the selected tile. If the selected
selected tile has no designation, they will be dig designated. tile has no designation, they will be dig designated. By default, only designates
visible tiles, and in the case of dig designation, applies automatic mining to them
(designates uncovered neighbouring tiles of the same type to be dug).
If an argument is given, the designation of the selected tile is ignored, and If an argument is given, the designation of the selected tile is ignored, and
all appropriate tiles are set to the specified designation. all appropriate tiles are set to the specified designation.
@ -143,9 +145,18 @@ Designation options:
``clear`` ``clear``
Clear any designations. Clear any designations.
You can also pass a ``-z`` option, which restricts designations to the current Other options:
z-level and down. This is useful when you don't want to designate tiles on the
same z-levels as your carefully dug fort above. ``--zdown``, ``-d``
Only designates tiles on the cursor's z-level and below.
``--zup``, ``-u``
Only designates tiles on the cursor's z-level and above.
``--cur-zlevel``, ``-z``
Only designates tiles on the same z-level as the cursor.
``--hidden``, ``-h``
Allows designation of hidden tiles, and picking a hidden tile as the target type.
``--no-auto``, ``-a``
No automatic mining mode designation - useful if you want to avoid dwarves digging where you don't want them.
digexp digexp
------ ------

@ -3,7 +3,7 @@ embark-tools
.. dfhack-tool:: .. dfhack-tool::
:summary: Extend the embark screen functionality. :summary: Extend the embark screen functionality.
:tags: unavailable embark fort interface
Usage Usage
----- -----

@ -17,6 +17,13 @@ Usage
manager orders. It will not clear the orders that already exist. manager orders. It will not clear the orders that already exist.
``orders clear`` ``orders clear``
Deletes all manager orders in the current embark. Deletes all manager orders in the current embark.
``orders recheck [this]``
Sets the status to ``Checking`` (from ``Active``) for all work orders. if the
"this" option is passed, only sets the status for the workorder whose condition
details page is open. This makes the manager reevaluate its conditions.
This is especially useful for an order that had its conditions met when it
was started, but the requisite items have since disappeared and the workorder
is now generating job cancellation spam.
``orders sort`` ``orders sort``
Sorts current manager orders by repeat frequency so repeating orders don't Sorts current manager orders by repeat frequency so repeating orders don't
prevent one-time orders from ever being completed. The sorting order is: prevent one-time orders from ever being completed. The sorting order is:

@ -3,9 +3,13 @@ tubefill
.. dfhack-tool:: .. dfhack-tool::
:summary: Replenishes mined-out adamantine. :summary: Replenishes mined-out adamantine.
:tags: unavailable fort armok map :tags: fort armok map
Veins that were originally hollow will be left alone. This tool replaces mined-out tiles of adamantine spires with fresh, undug
adamantine walls, ready to be re-harvested. Empty tiles within the spire that
used to contain special gemstones, obsidian, water, or magma will also be
replaced with fresh adamantine. Adamantine spires that were originally hollow
will be left hollow. See below for more details.
Usage Usage
----- -----

@ -1760,7 +1760,8 @@ static int textures_loadTileset(lua_State *state)
std::string file = luaL_checkstring(state, 1); std::string file = luaL_checkstring(state, 1);
auto tile_w = luaL_checkint(state, 2); auto tile_w = luaL_checkint(state, 2);
auto tile_h = luaL_checkint(state, 3); auto tile_h = luaL_checkint(state, 3);
auto handles = Textures::loadTileset(file, tile_w, tile_h); bool reserved = lua_isboolean(state, 4) ? lua_toboolean(state, 4) : false;
auto handles = Textures::loadTileset(file, tile_w, tile_h, reserved);
Lua::PushVector(state, handles); Lua::PushVector(state, handles);
return 1; return 1;
} }
@ -1798,7 +1799,8 @@ static int textures_createTile(lua_State *state)
Lua::GetVector(state, pixels); Lua::GetVector(state, pixels);
auto tile_w = luaL_checkint(state, 2); auto tile_w = luaL_checkint(state, 2);
auto tile_h = luaL_checkint(state, 3); auto tile_h = luaL_checkint(state, 3);
auto handle = Textures::createTile(pixels, tile_w, tile_h); bool reserved = lua_isboolean(state, 4) ? lua_toboolean(state, 4) : false;
auto handle = Textures::createTile(pixels, tile_w, tile_h, reserved);
Lua::Push(state, handle); Lua::Push(state, handle);
return 1; return 1;
} }
@ -1811,7 +1813,8 @@ static int textures_createTileset(lua_State *state)
auto texture_h = luaL_checkint(state, 3); auto texture_h = luaL_checkint(state, 3);
auto tile_w = luaL_checkint(state, 4); auto tile_w = luaL_checkint(state, 4);
auto tile_h = luaL_checkint(state, 5); auto tile_h = luaL_checkint(state, 5);
auto handles = Textures::createTileset(pixels, texture_w, texture_h, tile_w, tile_h); bool reserved = lua_isboolean(state, 6) ? lua_toboolean(state, 6) : false;
auto handles = Textures::createTileset(pixels, texture_w, texture_h, tile_w, tile_h, reserved);
Lua::PushVector(state, handles); Lua::PushVector(state, handles);
return 1; return 1;
} }

@ -131,12 +131,12 @@ void DFHack::Lua::GetVector(lua_State *state, std::vector<std::string> &pvec, in
} }
} }
static bool trigger_inhibit_l_down = false; static bool trigger_inhibit_l = false;
static bool trigger_inhibit_r_down = false; static bool trigger_inhibit_r = false;
static bool trigger_inhibit_m_down = false; static bool trigger_inhibit_m = false;
static bool inhibit_l_down = false; static bool inhibit_l = false;
static bool inhibit_r_down = false; static bool inhibit_r = false;
static bool inhibit_m_down = false; static bool inhibit_m = false;
void DFHack::Lua::PushInterfaceKeys(lua_State *L, void DFHack::Lua::PushInterfaceKeys(lua_State *L,
const std::set<df::interface_key> &keys) { const std::set<df::interface_key> &keys) {
@ -161,32 +161,32 @@ void DFHack::Lua::PushInterfaceKeys(lua_State *L,
} }
if (df::global::enabler) { if (df::global::enabler) {
if (!inhibit_l_down && df::global::enabler->mouse_lbut_down) { if (!inhibit_l && df::global::enabler->mouse_lbut) {
lua_pushboolean(L, true); lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_L_DOWN"); lua_setfield(L, -2, "_MOUSE_L");
trigger_inhibit_l_down = true; trigger_inhibit_l = true;
} }
if (!inhibit_r_down && df::global::enabler->mouse_rbut_down) { if (!inhibit_r && df::global::enabler->mouse_rbut) {
lua_pushboolean(L, true); lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_R_DOWN"); lua_setfield(L, -2, "_MOUSE_R");
trigger_inhibit_r_down = true; trigger_inhibit_r = true;
} }
if (!inhibit_m_down && df::global::enabler->mouse_mbut_down) { if (!inhibit_m && df::global::enabler->mouse_mbut) {
lua_pushboolean(L, true); lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_M_DOWN"); lua_setfield(L, -2, "_MOUSE_M");
trigger_inhibit_m_down = true; trigger_inhibit_m = true;
} }
if (df::global::enabler->mouse_lbut) { if (df::global::enabler->mouse_lbut_down) {
lua_pushboolean(L, true); lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_L"); lua_setfield(L, -2, "_MOUSE_L_DOWN");
} }
if (df::global::enabler->mouse_rbut) { if (df::global::enabler->mouse_rbut_down) {
lua_pushboolean(L, true); lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_R"); lua_setfield(L, -2, "_MOUSE_R_DOWN");
} }
if (df::global::enabler->mouse_mbut) { if (df::global::enabler->mouse_mbut_down) {
lua_pushboolean(L, true); lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_M"); lua_setfield(L, -2, "_MOUSE_M_DOWN");
} }
} }
} }
@ -2159,25 +2159,25 @@ void DFHack::Lua::Core::Reset(color_ostream &out, const char *where)
lua_settop(State, 0); lua_settop(State, 0);
} }
if (trigger_inhibit_l_down) { if (trigger_inhibit_l) {
trigger_inhibit_l_down = false; trigger_inhibit_l = false;
inhibit_l_down = true; inhibit_l = true;
} }
if (trigger_inhibit_r_down) { if (trigger_inhibit_r) {
trigger_inhibit_r_down = false; trigger_inhibit_r = false;
inhibit_r_down = true; inhibit_r = true;
} }
if (trigger_inhibit_m_down) { if (trigger_inhibit_m) {
trigger_inhibit_m_down = false; trigger_inhibit_m = false;
inhibit_m_down = true; inhibit_m = true;
} }
if (df::global::enabler) { if (df::global::enabler) {
if (!df::global::enabler->mouse_lbut) if (!df::global::enabler->mouse_lbut_down)
inhibit_l_down = false; inhibit_l = false;
if (!df::global::enabler->mouse_rbut) if (!df::global::enabler->mouse_rbut_down)
inhibit_r_down = false; inhibit_r = false;
if (!df::global::enabler->mouse_mbut) if (!df::global::enabler->mouse_mbut_down)
inhibit_m_down = false; inhibit_m = false;
} }
} }

@ -391,6 +391,7 @@ namespace DFHack
virtual ~dfhack_lua_viewscreen(); virtual ~dfhack_lua_viewscreen();
static df::viewscreen *get_pointer(lua_State *L, int idx, bool make); static df::viewscreen *get_pointer(lua_State *L, int idx, bool make);
static void markInputAsHandled();
virtual bool is_lua_screen() { return true; } virtual bool is_lua_screen() { return true; }
virtual bool isFocused() { return !defocused; } virtual bool isFocused() { return !defocused; }

@ -26,7 +26,7 @@ const uint32_t TILE_HEIGHT_PX = 12;
* Load texture and get handle. * Load texture and get handle.
* Keep it to obtain valid texpos. * Keep it to obtain valid texpos.
*/ */
DFHACK_EXPORT TexposHandle loadTexture(SDL_Surface* surface); DFHACK_EXPORT TexposHandle loadTexture(SDL_Surface* surface, bool reserved = false);
/** /**
* Load tileset from image file. * Load tileset from image file.
@ -34,7 +34,8 @@ DFHACK_EXPORT TexposHandle loadTexture(SDL_Surface* surface);
*/ */
DFHACK_EXPORT std::vector<TexposHandle> loadTileset(const std::string& file, DFHACK_EXPORT std::vector<TexposHandle> loadTileset(const std::string& file,
int tile_px_w = TILE_WIDTH_PX, int tile_px_w = TILE_WIDTH_PX,
int tile_px_h = TILE_HEIGHT_PX); int tile_px_h = TILE_HEIGHT_PX,
bool reserved = false);
/** /**
* Get texpos by handle. * Get texpos by handle.
@ -53,7 +54,7 @@ DFHACK_EXPORT void deleteHandle(TexposHandle handle);
* Register this texture and return TexposHandle. * Register this texture and return TexposHandle.
*/ */
DFHACK_EXPORT TexposHandle createTile(std::vector<uint32_t>& pixels, int tile_px_w = TILE_WIDTH_PX, DFHACK_EXPORT TexposHandle createTile(std::vector<uint32_t>& pixels, int tile_px_w = TILE_WIDTH_PX,
int tile_px_h = TILE_HEIGHT_PX); int tile_px_h = TILE_HEIGHT_PX, bool reserved = false);
/** /**
* Create new textures as tileset with RGBA32 format and pixels as data in row major order. * Create new textures as tileset with RGBA32 format and pixels as data in row major order.
@ -62,7 +63,8 @@ DFHACK_EXPORT TexposHandle createTile(std::vector<uint32_t>& pixels, int tile_px
DFHACK_EXPORT std::vector<TexposHandle> createTileset(std::vector<uint32_t>& pixels, DFHACK_EXPORT std::vector<TexposHandle> createTileset(std::vector<uint32_t>& pixels,
int texture_px_w, int texture_px_h, int texture_px_w, int texture_px_h,
int tile_px_w = TILE_WIDTH_PX, int tile_px_w = TILE_WIDTH_PX,
int tile_px_h = TILE_HEIGHT_PX); int tile_px_h = TILE_HEIGHT_PX,
bool reserved = false);
/** /**
* Call this on DFHack init just once to setup interposed handlers and * Call this on DFHack init just once to setup interposed handlers and

@ -15,8 +15,8 @@ TRANSPARENT_PEN = to_pen{tile=0, ch=0}
KEEP_LOWER_PEN = to_pen{ch=32, fg=0, bg=0, keep_lower=true} KEEP_LOWER_PEN = to_pen{ch=32, fg=0, bg=0, keep_lower=true}
local MOUSE_KEYS = { local MOUSE_KEYS = {
_MOUSE_L = true, _MOUSE_L = function(is_set) df.global.enabler.mouse_lbut = is_set and 1 or 0 end,
_MOUSE_R = true, _MOUSE_R = function(is_set) df.global.enabler.mouse_rbut = is_set and 1 or 0 end,
_MOUSE_M = true, _MOUSE_M = true,
_MOUSE_L_DOWN = true, _MOUSE_L_DOWN = true,
_MOUSE_R_DOWN = true, _MOUSE_R_DOWN = true,
@ -27,7 +27,7 @@ local FAKE_INPUT_KEYS = copyall(MOUSE_KEYS)
FAKE_INPUT_KEYS._STRING = true FAKE_INPUT_KEYS._STRING = true
function simulateInput(screen,...) function simulateInput(screen,...)
local keys = {} local keys, enabled_mouse_keys = {}, {}
local function push_key(arg) local function push_key(arg)
local kv = arg local kv = arg
if type(arg) == 'string' then if type(arg) == 'string' then
@ -35,6 +35,10 @@ function simulateInput(screen,...)
if kv == nil and not FAKE_INPUT_KEYS[arg] then if kv == nil and not FAKE_INPUT_KEYS[arg] then
error('Invalid keycode: '..arg) error('Invalid keycode: '..arg)
end end
if MOUSE_KEYS[arg] then
df.global.enabler.tracking_on = 1
enabled_mouse_keys[arg] = true
end
end end
if type(kv) == 'number' then if type(kv) == 'number' then
keys[#keys+1] = kv keys[#keys+1] = kv
@ -57,6 +61,11 @@ function simulateInput(screen,...)
end end
end end
end end
for mk, fn in pairs(MOUSE_KEYS) do
if type(fn) == 'function' then
fn(enabled_mouse_keys[mk])
end
end
dscreen._doSimulateInput(screen, keys) dscreen._doSimulateInput(screen, keys)
end end
@ -696,21 +705,6 @@ end
DEFAULT_INITIAL_PAUSE = true DEFAULT_INITIAL_PAUSE = true
local zscreen_inhibit_mouse_l = false
-- ensure underlying DF screens don't also react to handled clicks
function markMouseClicksHandled(keys)
if keys._MOUSE_L_DOWN then
-- note we can't clear mouse_lbut here. otherwise we break dragging,
df.global.enabler.mouse_lbut_down = 0
zscreen_inhibit_mouse_l = true
end
if keys._MOUSE_R_DOWN then
df.global.enabler.mouse_rbut_down = 0
df.global.enabler.mouse_rbut = 0
end
end
ZScreen = defclass(ZScreen, Screen) ZScreen = defclass(ZScreen, Screen)
ZScreen.ATTRS{ ZScreen.ATTRS{
defocusable=true, defocusable=true,
@ -789,37 +783,24 @@ function ZScreen:onInput(keys)
local has_mouse = self:isMouseOver() local has_mouse = self:isMouseOver()
if not self:hasFocus() then if not self:hasFocus() then
if has_mouse and if has_mouse and
(keys._MOUSE_L_DOWN or keys._MOUSE_R_DOWN or (keys._MOUSE_L or keys._MOUSE_R or
keys.CONTEXT_SCROLL_UP or keys.CONTEXT_SCROLL_DOWN or keys.CONTEXT_SCROLL_UP or keys.CONTEXT_SCROLL_DOWN or
keys.CONTEXT_SCROLL_PAGEUP or keys.CONTEXT_SCROLL_PAGEDOWN) then keys.CONTEXT_SCROLL_PAGEUP or keys.CONTEXT_SCROLL_PAGEDOWN) then
self:raise() self:raise()
else else
self:sendInputToParent(keys) self:sendInputToParent(keys)
return return true
end end
end end
if ZScreen.super.onInput(self, keys) then if ZScreen.super.onInput(self, keys) then
markMouseClicksHandled(keys) -- noop
return elseif self.pass_mouse_clicks and keys._MOUSE_L and not has_mouse then
end
if self.pass_mouse_clicks and keys._MOUSE_L_DOWN and not has_mouse then
self.defocused = self.defocusable self.defocused = self.defocusable
self:sendInputToParent(keys) self:sendInputToParent(keys)
return elseif keys.LEAVESCREEN or keys._MOUSE_R then
elseif keys.LEAVESCREEN or keys._MOUSE_R_DOWN then
self:dismiss() self:dismiss()
markMouseClicksHandled(keys)
return
else
if zscreen_inhibit_mouse_l then
if keys._MOUSE_L then
return
else else
zscreen_inhibit_mouse_l = false
end
end
local passit = self.pass_pause and keys.D_PAUSE local passit = self.pass_pause and keys.D_PAUSE
if not passit and self.pass_mouse_clicks then if not passit and self.pass_mouse_clicks then
if keys.CONTEXT_SCROLL_UP or keys.CONTEXT_SCROLL_DOWN or if keys.CONTEXT_SCROLL_UP or keys.CONTEXT_SCROLL_DOWN or
@ -840,8 +821,8 @@ function ZScreen:onInput(keys)
if passit then if passit then
self:sendInputToParent(keys) self:sendInputToParent(keys)
end end
return
end end
return true
end end
function ZScreen:raise() function ZScreen:raise()

@ -4,8 +4,6 @@ local _ENV = mkmodule('gui.buildings')
local gui = require('gui') local gui = require('gui')
local widgets = require('gui.widgets') local widgets = require('gui.widgets')
local dlg = require('gui.dialogs')
local utils = require('utils')
ARROW = string.char(26) ARROW = string.char(26)

@ -57,14 +57,13 @@ function MessageBox:onDestroy()
end end
function MessageBox:onInput(keys) function MessageBox:onInput(keys)
if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R_DOWN then if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R then
self:dismiss() self:dismiss()
if keys.SELECT and self.on_accept then if keys.SELECT and self.on_accept then
self.on_accept() self.on_accept()
elseif (keys.LEAVESCREEN or keys._MOUSE_R_DOWN) and self.on_cancel then elseif (keys.LEAVESCREEN or keys._MOUSE_R) and self.on_cancel then
self.on_cancel() self.on_cancel()
end end
gui.markMouseClicksHandled(keys)
return true return true
end end
return self:inputToSubviews(keys) return self:inputToSubviews(keys)
@ -129,12 +128,11 @@ function InputBox:onInput(keys)
self.on_input(self.subviews.edit.text) self.on_input(self.subviews.edit.text)
end end
return true return true
elseif keys.LEAVESCREEN or keys._MOUSE_R_DOWN then elseif keys.LEAVESCREEN or keys._MOUSE_R then
self:dismiss() self:dismiss()
if self.on_cancel then if self.on_cancel then
self.on_cancel() self.on_cancel()
end end
gui.markMouseClicksHandled(keys)
return true return true
end end
return self:inputToSubviews(keys) return self:inputToSubviews(keys)
@ -231,12 +229,11 @@ function ListBox:getWantedFrameSize()
end end
function ListBox:onInput(keys) function ListBox:onInput(keys)
if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then if keys.LEAVESCREEN or keys._MOUSE_R then
self:dismiss() self:dismiss()
if self.on_cancel then if self.on_cancel then
self.on_cancel() self.on_cancel()
end end
gui.markMouseClicksHandled(keys)
return true return true
end end
return self:inputToSubviews(keys) return self:inputToSubviews(keys)

@ -5,7 +5,6 @@ local _ENV = mkmodule('gui.materials')
local gui = require('gui') local gui = require('gui')
local widgets = require('gui.widgets') local widgets = require('gui.widgets')
local dlg = require('gui.dialogs') local dlg = require('gui.dialogs')
local utils = require('utils')
ARROW = string.char(26) ARROW = string.char(26)

@ -6,18 +6,18 @@ local _ENV = mkmodule('gui.textures')
-- Preloaded DFHack Assets. -- Preloaded DFHack Assets.
-- Use this handles if you need to get dfhack standard textures. -- Use this handles if you need to get dfhack standard textures.
---@type table<string, TexposHandle> ---@type table<string, TexposHandle[]>
local texpos_handles = { local texpos_handles = {
green_pin = dfhack.textures.loadTileset('hack/data/art/green-pin.png', 8, 12), green_pin = dfhack.textures.loadTileset('hack/data/art/green-pin.png', 8, 12, true),
red_pin = dfhack.textures.loadTileset('hack/data/art/red-pin.png', 8, 12), red_pin = dfhack.textures.loadTileset('hack/data/art/red-pin.png', 8, 12, true),
icons = dfhack.textures.loadTileset('hack/data/art/icons.png', 8, 12), icons = dfhack.textures.loadTileset('hack/data/art/icons.png', 8, 12, true),
on_off = dfhack.textures.loadTileset('hack/data/art/on-off.png', 8, 12), on_off = dfhack.textures.loadTileset('hack/data/art/on-off.png', 8, 12, true),
control_panel = dfhack.textures.loadTileset('hack/data/art/control-panel.png', 8, 12), control_panel = dfhack.textures.loadTileset('hack/data/art/control-panel.png', 8, 12, true),
border_thin = dfhack.textures.loadTileset('hack/data/art/border-thin.png', 8, 12), border_thin = dfhack.textures.loadTileset('hack/data/art/border-thin.png', 8, 12, true),
border_medium = dfhack.textures.loadTileset('hack/data/art/border-medium.png', 8, 12), border_medium = dfhack.textures.loadTileset('hack/data/art/border-medium.png', 8, 12, true),
border_bold = dfhack.textures.loadTileset('hack/data/art/border-bold.png', 8, 12), border_bold = dfhack.textures.loadTileset('hack/data/art/border-bold.png', 8, 12, true),
border_panel = dfhack.textures.loadTileset('hack/data/art/border-panel.png', 8, 12), border_panel = dfhack.textures.loadTileset('hack/data/art/border-panel.png', 8, 12, true),
border_window = dfhack.textures.loadTileset('hack/data/art/border-window.png', 8, 12), border_window = dfhack.textures.loadTileset('hack/data/art/border-window.png', 8, 12, true),
} }
-- Get valid texpos for preloaded texture in tileset -- Get valid texpos for preloaded texture in tileset

@ -273,7 +273,7 @@ end
function Panel:onInput(keys) function Panel:onInput(keys)
if self.kbd_get_pos then if self.kbd_get_pos then
if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R_DOWN then if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R then
Panel_end_drag(self, not keys.SELECT and self.saved_frame or nil, Panel_end_drag(self, not keys.SELECT and self.saved_frame or nil,
not not keys.SELECT) not not keys.SELECT)
return true return true
@ -281,7 +281,6 @@ function Panel:onInput(keys)
for code in pairs(keys) do for code in pairs(keys) do
local dx, dy = guidm.get_movement_delta(code, 1, 10) local dx, dy = guidm.get_movement_delta(code, 1, 10)
if dx then if dx then
local frame_rect = self.frame_rect
local kbd_pos = self.kbd_get_pos() local kbd_pos = self.kbd_get_pos()
kbd_pos.x = kbd_pos.x + dx kbd_pos.x = kbd_pos.x + dx
kbd_pos.y = kbd_pos.y + dy kbd_pos.y = kbd_pos.y + dy
@ -292,9 +291,9 @@ function Panel:onInput(keys)
return return
end end
if self.drag_offset then if self.drag_offset then
if keys._MOUSE_R_DOWN then if keys._MOUSE_R then
Panel_end_drag(self, self.saved_frame) Panel_end_drag(self, self.saved_frame)
elseif keys._MOUSE_L then elseif keys._MOUSE_L_DOWN then
Panel_update_frame(self, Panel_make_frame(self)) Panel_update_frame(self, Panel_make_frame(self))
end end
return true return true
@ -302,7 +301,7 @@ function Panel:onInput(keys)
if Panel.super.onInput(self, keys) then if Panel.super.onInput(self, keys) then
return true return true
end end
if not keys._MOUSE_L_DOWN then return end if not keys._MOUSE_L then return end
local x,y = self:getMouseFramePos() local x,y = self:getMouseFramePos()
if not x then return end if not x then return end
@ -489,7 +488,7 @@ function Panel:onRenderFrame(dc, rect)
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
and df.global.enabler.mouse_lbut == 0 then and df.global.enabler.mouse_lbut_down == 0 then
Panel_end_drag(self, nil, true) Panel_end_drag(self, nil, true)
end end
end end
@ -718,7 +717,7 @@ function EditField:onInput(keys)
end end
end end
if self.key and (keys.LEAVESCREEN or keys._MOUSE_R_DOWN) then if self.key and (keys.LEAVESCREEN or keys._MOUSE_R) then
self:setText(self.saved_text) self:setText(self.saved_text)
self:setFocus(false) self:setFocus(false)
return true return true
@ -740,8 +739,8 @@ function EditField:onInput(keys)
end end
end end
return not not self.key return not not self.key
elseif keys._MOUSE_L then elseif keys._MOUSE_L_DOWN then
local mouse_x, mouse_y = self:getMousePos() local mouse_x = self:getMousePos()
if mouse_x then if mouse_x then
self:setCursor(self.start_pos + mouse_x - (self.text_offset or 0)) self:setCursor(self.start_pos + mouse_x - (self.text_offset or 0))
return true return true
@ -986,7 +985,7 @@ function Scrollbar:onRenderBody(dc)
if self.is_dragging then if self.is_dragging then
scrollbar_do_drag(self) scrollbar_do_drag(self)
end end
if df.global.enabler.mouse_lbut == 0 then if df.global.enabler.mouse_lbut_down == 0 then
self.last_scroll_ms = 0 self.last_scroll_ms = 0
self.is_dragging = false self.is_dragging = false
self.scroll_spec = nil self.scroll_spec = nil
@ -1023,7 +1022,7 @@ function Scrollbar:onInput(keys)
return true return true
end end
end end
if not keys._MOUSE_L_DOWN then return false end if not keys._MOUSE_L 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
@ -1386,11 +1385,11 @@ function Label:onInput(keys)
if self:inputToSubviews(keys) then if self:inputToSubviews(keys) then
return true return true
end end
if keys._MOUSE_L_DOWN and self:getMousePos() and self.on_click then if keys._MOUSE_L and self:getMousePos() and self.on_click then
self.on_click() self.on_click()
return true return true
end end
if keys._MOUSE_R_DOWN and self:getMousePos() and self.on_rclick then if keys._MOUSE_R and self:getMousePos() and self.on_rclick then
self.on_rclick() self.on_rclick()
return true return true
end end
@ -1498,7 +1497,7 @@ end
function HotkeyLabel:onInput(keys) function HotkeyLabel:onInput(keys)
if HotkeyLabel.super.onInput(self, keys) then if HotkeyLabel.super.onInput(self, keys) then
return true return true
elseif keys._MOUSE_L_DOWN and self:getMousePos() and self.on_activate elseif keys._MOUSE_L and self:getMousePos() and self.on_activate
and not is_disabled(self) then and not is_disabled(self) then
self.on_activate() self.on_activate()
return true return true
@ -1658,7 +1657,7 @@ end
function CycleHotkeyLabel:onInput(keys) function CycleHotkeyLabel:onInput(keys)
if CycleHotkeyLabel.super.onInput(self, keys) then if CycleHotkeyLabel.super.onInput(self, keys) then
return true return true
elseif keys._MOUSE_L_DOWN and self:getMousePos() and not is_disabled(self) then elseif keys._MOUSE_L and self:getMousePos() and not is_disabled(self) then
self:cycle() self:cycle()
return true return true
end end
@ -1962,7 +1961,7 @@ function List:onInput(keys)
return self:submit() return self:submit()
elseif keys.CUSTOM_SHIFT_ENTER then elseif keys.CUSTOM_SHIFT_ENTER then
return self:submit2() return self:submit2()
elseif keys._MOUSE_L_DOWN then elseif keys._MOUSE_L then
local idx = self:getIdxUnderMouse() local idx = self:getIdxUnderMouse()
if idx then if idx then
local now_ms = dfhack.getTickCount() local now_ms = dfhack.getTickCount()
@ -2317,7 +2316,7 @@ end
function Tab:onInput(keys) function Tab:onInput(keys)
if Tab.super.onInput(self, keys) then return true end if Tab.super.onInput(self, keys) then return true end
if keys._MOUSE_L_DOWN and self:getMousePos() then if keys._MOUSE_L and self:getMousePos() then
self.on_select(self.id) self.on_select(self.id)
return true return true
end end
@ -2419,7 +2418,7 @@ local function rangeslider_get_width_per_idx(self)
end end
function RangeSlider:onInput(keys) function RangeSlider:onInput(keys)
if not keys._MOUSE_L_DOWN then return false end if not keys._MOUSE_L then return false end
local x = self:getMousePos() local x = self:getMousePos()
if not x then return false end if not x then return false end
local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn() local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn()
@ -2527,7 +2526,7 @@ function RangeSlider:onRenderBody(dc, rect)
if self.is_dragging_target then if self.is_dragging_target then
rangeslider_do_drag(self, width_per_idx) rangeslider_do_drag(self, width_per_idx)
end end
if df.global.enabler.mouse_lbut == 0 then if df.global.enabler.mouse_lbut_down == 0 then
self.is_dragging_target = nil self.is_dragging_target = nil
self.is_dragging_idx = nil self.is_dragging_idx = nil
end end

@ -43,6 +43,7 @@
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <array> #include <array>
#include <utility>
namespace DFHack { namespace DFHack {
DBG_DECLARE(eventmanager, log, DebugCategory::LINFO); DBG_DECLARE(eventmanager, log, DebugCategory::LINFO);
@ -246,6 +247,15 @@ static int32_t reportToRelevantUnitsTime = -1;
//interaction //interaction
static int32_t lastReportInteraction; static int32_t lastReportInteraction;
struct hash_pair {
template<typename A, typename B>
size_t operator()(const std::pair<A,B>& p) const {
auto h1 = std::hash<A>{}(p.first);
auto h2 = std::hash<B>{}(p.second);
return h1 ^ (h2 << 1);
}
};
void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) {
static bool doOnce = false; static bool doOnce = false;
// const string eventNames[] = {"world loaded", "world unloaded", "map loaded", "map unloaded", "viewscreen changed", "core initialized", "begin unload", "paused", "unpaused"}; // const string eventNames[] = {"world loaded", "world unloaded", "map loaded", "map unloaded", "viewscreen changed", "core initialized", "begin unload", "paused", "unpaused"};
@ -276,9 +286,9 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event
gameLoaded = false; gameLoaded = false;
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNLOAD].begin(), handlers[EventType::UNLOAD].end()); multimap<Plugin*,EventHandler> copy(handlers[EventType::UNLOAD].begin(), handlers[EventType::UNLOAD].end());
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for map unloaded state change event\n"); DEBUG(log,out).print("calling handler for map unloaded state change event\n");
key_value.second.eventHandler(out, nullptr); handle.eventHandler(out, nullptr);
} }
} else if ( event == DFHack::SC_MAP_LOADED ) { } else if ( event == DFHack::SC_MAP_LOADED ) {
/* /*
@ -375,8 +385,7 @@ void DFHack::EventManager::manageEvents(color_ostream& out) {
continue; continue;
int32_t eventFrequency = -100; int32_t eventFrequency = -100;
if ( a != EventType::TICK ) if ( a != EventType::TICK )
for (auto &key_value : handlers[a]) { for (auto &[_,handle] : handlers[a]) {
EventHandler &handle = key_value.second;
if (handle.freq < eventFrequency || eventFrequency == -100 ) if (handle.freq < eventFrequency || eventFrequency == -100 )
eventFrequency = handle.freq; eventFrequency = handle.freq;
} }
@ -439,8 +448,7 @@ static void manageJobInitiatedEvent(color_ostream& out) {
continue; continue;
if ( link->item->id <= lastJobId ) if ( link->item->id <= lastJobId )
continue; continue;
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for job initiated event\n"); DEBUG(log,out).print("calling handler for job initiated event\n");
handle.eventHandler(out, (void*)link->item); handle.eventHandler(out, (void*)link->item);
} }
@ -455,17 +463,24 @@ static void manageJobStartedEvent(color_ostream& out) {
static unordered_set<int32_t> startedJobs; static unordered_set<int32_t> startedJobs;
vector<df::job*> new_started_jobs;
// iterate event handler callbacks // iterate event handler callbacks
multimap<Plugin*, EventHandler> copy(handlers[EventType::JOB_STARTED].begin(), handlers[EventType::JOB_STARTED].end()); multimap<Plugin*, EventHandler> copy(handlers[EventType::JOB_STARTED].begin(), handlers[EventType::JOB_STARTED].end());
for (df::job_list_link* link = df::global::world->jobs.list.next; link != nullptr; link = link->next) {
df::job* job = link->item; for (df::job_list_link* link = &df::global::world->jobs.list; link->next != nullptr; link = link->next) {
df::job* job = link->next->item;
int32_t j_id = job->id;
if (job && Job::getWorker(job) && !startedJobs.count(job->id)) { if (job && Job::getWorker(job) && !startedJobs.count(job->id)) {
startedJobs.emplace(job->id); startedJobs.emplace(job->id);
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
auto &handler = key_value.second;
// the jobs must have a worker to start // the jobs must have a worker to start
DEBUG(log,out).print("calling handler for job started event\n"); DEBUG(log,out).print("calling handler for job started event\n");
handler.eventHandler(out, job); handle.eventHandler(out, job);
}
}
if (link->next == nullptr || link->next->item->id != j_id) {
if ( Once::doOnce("EventManager jobstarted job removed") ) {
out.print("%s,%d: job %u removed from jobs linked list\n", __FILE__, __LINE__, j_id);
} }
} }
} }
@ -487,11 +502,11 @@ static void manageJobCompletedEvent(color_ostream& out) {
int32_t tick1 = df::global::world->frame_counter; int32_t tick1 = df::global::world->frame_counter;
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end()); multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end());
map<int32_t, df::job*> nowJobs; unordered_map<int32_t, df::job*> nowJobs;
for ( df::job_list_link* link = &df::global::world->jobs.list; link != nullptr; link = link->next ) { for ( df::job_list_link* link = &df::global::world->jobs.list; link != nullptr; link = link->next ) {
if ( link->item == nullptr ) if ( link->item == nullptr )
continue; continue;
nowJobs[link->item->id] = link->item; nowJobs.emplace(link->item->id, link->item);
} }
#if 0 #if 0
@ -573,8 +588,7 @@ static void manageJobCompletedEvent(color_ostream& out) {
continue; continue;
//still false positive if cancelled at EXACTLY the right time, but experiments show this doesn't happen //still false positive if cancelled at EXACTLY the right time, but experiments show this doesn't happen
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for repeated job completed event\n"); DEBUG(log,out).print("calling handler for repeated job completed event\n");
handle.eventHandler(out, (void*) &job0); handle.eventHandler(out, (void*) &job0);
} }
@ -586,28 +600,27 @@ static void manageJobCompletedEvent(color_ostream& out) {
if ( job0.flags.bits.repeat || job0.completion_timer != 0 ) if ( job0.flags.bits.repeat || job0.completion_timer != 0 )
continue; continue;
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for job completed event\n"); DEBUG(log,out).print("calling handler for job completed event\n");
handle.eventHandler(out, (void*) &job0); handle.eventHandler(out, (void*) &job0);
} }
} }
//erase old jobs, copy over possibly altered jobs //erase old jobs, copy over possibly altered jobs
for (auto &prevJob : prevJobs) { for (auto &[_,prev_job] : prevJobs) {
Job::deleteJobStruct(prevJob.second, true); Job::deleteJobStruct(prev_job, true);
} }
prevJobs.clear(); prevJobs.clear();
//create new jobs //create new jobs
for (auto &nowJob : nowJobs) { for (auto &[_,now_job] : nowJobs) {
/*map<int32_t, df::job*>::iterator i = prevJobs.find((*j).first); /*map<int32_t, df::job*>::iterator i = prevJobs.find((*j).first);
if ( i != prevJobs.end() ) { if ( i != prevJobs.end() ) {
continue; continue;
}*/ }*/
df::job* newJob = Job::cloneJobStruct(nowJob.second, true); df::job* newJob = Job::cloneJobStruct(now_job, true);
prevJobs[newJob->id] = newJob; prevJobs.emplace(newJob->id, newJob);
} }
} }
@ -617,16 +630,18 @@ static void manageNewUnitActiveEvent(color_ostream& out) {
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_NEW_ACTIVE].begin(), handlers[EventType::UNIT_NEW_ACTIVE].end()); multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_NEW_ACTIVE].begin(), handlers[EventType::UNIT_NEW_ACTIVE].end());
// iterate event handler callbacks // iterate event handler callbacks
for (auto &key_value : copy) { vector<int32_t> new_active_unit_ids;
auto &handler = key_value.second;
for (df::unit* unit : df::global::world->units.active) { for (df::unit* unit : df::global::world->units.active) {
int32_t id = unit->id; if (!activeUnits.count(unit->id)) {
if (!activeUnits.count(id)) { activeUnits.emplace(unit->id);
activeUnits.emplace(id); new_active_unit_ids.emplace_back(unit->id);
DEBUG(log,out).print("calling handler for new unit event\n");
handler.eventHandler(out, (void*) intptr_t(id)); // intptr_t() avoids cast from smaller type warning
} }
} }
for (int32_t unit_id : new_active_unit_ids) {
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for new unit event\n");
handle.eventHandler(out, (void*) intptr_t(unit_id)); // intptr_t() avoids cast from smaller type warning
}
} }
} }
@ -635,22 +650,26 @@ static void manageUnitDeathEvent(color_ostream& out) {
if (!df::global::world) if (!df::global::world)
return; return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end()); multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end());
vector<int32_t> dead_unit_ids;
for (auto unit : df::global::world->units.all) { for (auto unit : df::global::world->units.all) {
//if ( unit->counters.death_id == -1 ) { //if ( unit->counters.death_id == -1 ) {
if ( Units::isActive(unit) ) { if ( Units::isActive(unit) ) {
livingUnits.insert(unit->id); livingUnits.insert(unit->id);
continue; continue;
} }
if (!Units::isDead(unit)) continue; // for units that have left the map but aren't dead
//dead: if dead since last check, trigger events //dead: if dead since last check, trigger events
if ( livingUnits.find(unit->id) == livingUnits.end() ) if ( livingUnits.find(unit->id) == livingUnits.end() )
continue; continue;
livingUnits.erase(unit->id);
dead_unit_ids.emplace_back(unit->id);
}
for (auto &key_value : copy) { for (int32_t unit_id : dead_unit_ids) {
EventHandler &handle = key_value.second; for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit death event\n"); DEBUG(log,out).print("calling handler for unit death event\n");
handle.eventHandler(out, (void*)intptr_t(unit->id)); handle.eventHandler(out, (void*)intptr_t(unit_id));
} }
livingUnits.erase(unit->id);
} }
} }
@ -666,6 +685,8 @@ static void manageItemCreationEvent(color_ostream& out) {
multimap<Plugin*,EventHandler> copy(handlers[EventType::ITEM_CREATED].begin(), handlers[EventType::ITEM_CREATED].end()); multimap<Plugin*,EventHandler> copy(handlers[EventType::ITEM_CREATED].begin(), handlers[EventType::ITEM_CREATED].end());
size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false); size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false);
if ( index != 0 ) index--; if ( index != 0 ) index--;
std::vector<int32_t> created_items;
for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) { for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) {
df::item* item = df::global::world->items.all[a]; df::item* item = df::global::world->items.all[a];
//already processed //already processed
@ -683,12 +704,17 @@ static void manageItemCreationEvent(color_ostream& out) {
//spider webs don't count //spider webs don't count
if ( item->flags.bits.spider_web ) if ( item->flags.bits.spider_web )
continue; continue;
for (auto &key_value : copy) { created_items.push_back(item->id);
EventHandler &handle = key_value.second; }
// handle all created items
for (int32_t item_id : created_items) {
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for item created event\n"); DEBUG(log,out).print("calling handler for item created event\n");
handle.eventHandler(out, (void*)intptr_t(item->id)); handle.eventHandler(out, (void*)intptr_t(item_id));
} }
} }
nextItem = *df::global::item_next_id; nextItem = *df::global::item_next_id;
} }
@ -703,6 +729,7 @@ static void manageBuildingEvent(color_ostream& out) {
**/ **/
multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end()); multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end());
//first alert people about new buildings //first alert people about new buildings
vector<int32_t> new_buildings;
for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) { for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) {
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a); int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a);
if ( index == -1 ) { if ( index == -1 ) {
@ -711,30 +738,34 @@ static void manageBuildingEvent(color_ostream& out) {
continue; continue;
} }
buildings.insert(a); buildings.insert(a);
for (auto &key_value : copy) { new_buildings.emplace_back(a);
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for created building event\n");
handle.eventHandler(out, (void*)intptr_t(a));
}
} }
nextBuilding = *df::global::building_next_id; nextBuilding = *df::global::building_next_id;
//now alert people about destroyed buildings //now alert people about destroyed buildings
for ( auto a = buildings.begin(); a != buildings.end(); ) { for ( auto it = buildings.begin(); it != buildings.end(); ) {
int32_t id = *a; int32_t id = *it;
int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id); int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id);
if ( index != -1 ) { if ( index != -1 ) {
a++; ++it;
continue; continue;
} }
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for destroyed building event\n"); DEBUG(log,out).print("calling handler for destroyed building event\n");
handle.eventHandler(out, (void*)intptr_t(id)); handle.eventHandler(out, (void*)intptr_t(id));
} }
a = buildings.erase(a); it = buildings.erase(it);
} }
//alert people about newly created buildings
std::for_each(new_buildings.begin(), new_buildings.end(), [&](int32_t building){
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for created building event\n");
handle.eventHandler(out, (void*)intptr_t(building));
}
});
} }
static void manageConstructionEvent(color_ostream& out) { static void manageConstructionEvent(color_ostream& out) {
@ -743,44 +774,52 @@ static void manageConstructionEvent(color_ostream& out) {
//unordered_set<df::construction*> constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end()); //unordered_set<df::construction*> constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end());
multimap<Plugin*, EventHandler> copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end()); multimap<Plugin*, EventHandler> copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end());
// find & send construction removals
for (auto iter = constructions.begin(); iter != constructions.end();) { unordered_set<df::construction> next_construction_set; // will be swapped with constructions
auto &construction = *iter; next_construction_set.reserve(constructions.bucket_count());
// if we can't find it, it was removed vector<df::construction> new_constructions;
if (df::construction::find(construction.pos) != nullptr) {
++iter; // find new constructions - swapping found constructions over from constructions to next_construction_set
continue; for (auto c : df::global::world->constructions) {
auto &construction = *c;
auto it = constructions.find(construction);
if (it == constructions.end()) {
// handle new construction event later
new_constructions.emplace_back(construction);
}
else {
constructions.erase(it);
} }
// send construction to handlers, because it was removed next_construction_set.emplace(construction);
for (const auto &key_value: copy) { }
EventHandler handle = key_value.second;
constructions.swap(next_construction_set);
// now next_construction_set contains all the constructions that were removed (not found in df::global::world->constructions)
for (auto& construction : next_construction_set) {
// handle construction removed event
for (const auto &[_,handle]: copy) {
DEBUG(log,out).print("calling handler for destroyed construction event\n"); DEBUG(log,out).print("calling handler for destroyed construction event\n");
handle.eventHandler(out, (void*) &construction); handle.eventHandler(out, (void*) &construction);
} }
// erase from existent constructions
iter = constructions.erase(iter);
} }
// find & send construction additions // now handle all the new constructions
for (auto c: df::global::world->constructions) { for (auto& construction : new_constructions) {
auto &construction = *c; for (const auto &[_,handle]: copy) {
// add construction to constructions, if it isn't already present
if (constructions.emplace(construction).second) {
// send construction to handlers, because it is new
for (const auto &key_value: copy) {
EventHandler handle = key_value.second;
DEBUG(log,out).print("calling handler for created construction event\n"); DEBUG(log,out).print("calling handler for created construction event\n");
handle.eventHandler(out, (void*) &construction); handle.eventHandler(out, (void*) &construction);
} }
} }
} }
}
static void manageSyndromeEvent(color_ostream& out) { static void manageSyndromeEvent(color_ostream& out) {
if (!df::global::world) if (!df::global::world)
return; return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end()); multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end());
int32_t highestTime = -1; int32_t highestTime = -1;
std::vector<SyndromeData> new_syndrome_data;
for (auto unit : df::global::world->units.all) { for (auto unit : df::global::world->units.all) {
/* /*
@ -795,14 +834,16 @@ static void manageSyndromeEvent(color_ostream& out) {
if ( startTime <= lastSyndromeTime ) if ( startTime <= lastSyndromeTime )
continue; continue;
SyndromeData data(unit->id, b); new_syndrome_data.emplace_back(unit->id, b);
for (auto &key_value : copy) { }
EventHandler &handle = key_value.second; }
for (auto& data : new_syndrome_data) {
for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for syndrome event\n"); DEBUG(log,out).print("calling handler for syndrome event\n");
handle.eventHandler(out, (void*)&data); handle.eventHandler(out, (void*)&data);
} }
} }
}
lastSyndromeTime = highestTime; lastSyndromeTime = highestTime;
} }
@ -815,8 +856,7 @@ static void manageInvasionEvent(color_ostream& out) {
return; return;
nextInvasion = df::global::plotinfo->invasions.next_id; nextInvasion = df::global::plotinfo->invasions.next_id;
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for invasion event\n"); DEBUG(log,out).print("calling handler for invasion event\n");
handle.eventHandler(out, (void*)intptr_t(nextInvasion-1)); handle.eventHandler(out, (void*)intptr_t(nextInvasion-1));
} }
@ -829,6 +869,18 @@ static void manageEquipmentEvent(color_ostream& out) {
unordered_map<int32_t, InventoryItem> itemIdToInventoryItem; unordered_map<int32_t, InventoryItem> itemIdToInventoryItem;
unordered_set<int32_t> currentlyEquipped; unordered_set<int32_t> currentlyEquipped;
vector<InventoryChangeData> equipment_pickups;
vector<InventoryChangeData> equipment_drops;
vector<InventoryChangeData> equipment_changes;
// This vector stores the pointers to newly created changed items
// needed as the stack allocated temporary (in the loop) is lost when we go to
// handle the event calls, so we move that data to the heap if its needed,
// and then once we are done we delete everything.
vector<InventoryItem*> changed_items;
for (auto unit : df::global::world->units.all) { for (auto unit : df::global::world->units.all) {
itemIdToInventoryItem.clear(); itemIdToInventoryItem.clear();
currentlyEquipped.clear(); currentlyEquipped.clear();
@ -856,40 +908,30 @@ static void manageEquipmentEvent(color_ostream& out) {
auto c = itemIdToInventoryItem.find(dfitem_new->item->id); auto c = itemIdToInventoryItem.find(dfitem_new->item->id);
if ( c == itemIdToInventoryItem.end() ) { if ( c == itemIdToInventoryItem.end() ) {
//new item equipped (probably just picked up) //new item equipped (probably just picked up)
InventoryChangeData data(unit->id, nullptr, &item_new); changed_items.emplace_back(new InventoryItem(item_new));
for (auto &key_value : copy) { equipment_pickups.emplace_back(unit->id, nullptr, changed_items.back());
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for new item equipped inventory change event\n");
handle.eventHandler(out, (void*)&data);
}
continue; continue;
} }
InventoryItem item_old = (*c).second; InventoryItem item_old = c->second;
df::unit_inventory_item& item0 = item_old.item; df::unit_inventory_item& item0 = item_old.item;
df::unit_inventory_item& item1 = item_new.item; df::unit_inventory_item& item1 = item_new.item;
if ( item0.mode == item1.mode && item0.body_part_id == item1.body_part_id && item0.wound_id == item1.wound_id ) if ( item0.mode == item1.mode && item0.body_part_id == item1.body_part_id && item0.wound_id == item1.wound_id )
continue; continue;
//some sort of change in how it's equipped //some sort of change in how it's equipped
changed_items.emplace_back(new InventoryItem(item_new));
InventoryChangeData data(unit->id, &item_old, &item_new); InventoryItem* item_new_ptr = changed_items.back();
for (auto &key_value : copy) { changed_items.emplace_back(new InventoryItem(item_old));
EventHandler &handle = key_value.second; InventoryItem* item_old_ptr = changed_items.back();
DEBUG(log,out).print("calling handler for inventory change event\n"); equipment_changes.emplace_back(unit->id, item_old_ptr, item_new_ptr);
handle.eventHandler(out, (void*)&data);
}
} }
//check for dropped items //check for dropped items
for (auto i : v) { for (auto i : v) {
if ( currentlyEquipped.find(i.itemId) != currentlyEquipped.end() ) if ( currentlyEquipped.find(i.itemId) != currentlyEquipped.end() )
continue; continue;
//TODO: delete ptr if invalid //TODO: delete ptr if invalid
InventoryChangeData data(unit->id, &i, nullptr); changed_items.emplace_back(new InventoryItem(i));
for (auto &key_value : copy) { equipment_drops.emplace_back(unit->id, changed_items.back(), nullptr);
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for dropped item inventory change event\n");
handle.eventHandler(out, (void*)&data);
}
} }
if ( !hadEquipment ) if ( !hadEquipment )
delete temp; delete temp;
@ -902,6 +944,31 @@ static void manageEquipmentEvent(color_ostream& out) {
equipment.push_back(item); equipment.push_back(item);
} }
} }
// now handle events
std::for_each(equipment_pickups.begin(), equipment_pickups.end(), [&](InventoryChangeData& data) {
for (auto &[_, handle] : copy) {
DEBUG(log,out).print("calling handler for new item equipped inventory change event\n");
handle.eventHandler(out, (void*) &data);
}
});
std::for_each(equipment_drops.begin(), equipment_drops.end(), [&](InventoryChangeData& data) {
for (auto &[_, handle] : copy) {
DEBUG(log,out).print("calling handler for dropped item inventory change event\n");
handle.eventHandler(out, (void*) &data);
}
});
std::for_each(equipment_changes.begin(), equipment_changes.end(), [&](InventoryChangeData& data) {
for (auto &[_, handle] : copy) {
DEBUG(log,out).print("calling handler for inventory change event\n");
handle.eventHandler(out, (void*) &data);
}
});
// clean up changed items list
std::for_each(changed_items.begin(), changed_items.end(), [](InventoryItem* p){
delete p;
});
} }
static void updateReportToRelevantUnits() { static void updateReportToRelevantUnits() {
@ -939,8 +1006,7 @@ static void manageReportEvent(color_ostream& out) {
for ( ; idx < reports.size(); idx++ ) { for ( ; idx < reports.size(); idx++ ) {
df::report* report = reports[idx]; df::report* report = reports[idx];
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for report event\n"); DEBUG(log,out).print("calling handler for report event\n");
handle.eventHandler(out, (void*)intptr_t(report->id)); handle.eventHandler(out, (void*)intptr_t(report->id));
} }
@ -981,7 +1047,7 @@ static void manageUnitAttackEvent(color_ostream& out) {
if ( strikeReports.empty() ) if ( strikeReports.empty() )
return; return;
updateReportToRelevantUnits(); updateReportToRelevantUnits();
map<int32_t, map<int32_t, int32_t> > alreadyDone; unordered_set<std::pair<int32_t, int32_t>, hash_pair> already_done;
for (int reportId : strikeReports) { for (int reportId : strikeReports) {
df::report* report = df::report::find(reportId); df::report* report = df::report::find(reportId);
if ( !report ) if ( !report )
@ -1011,27 +1077,25 @@ static void manageUnitAttackEvent(color_ostream& out) {
UnitAttackData data{}; UnitAttackData data{};
data.report_id = report->id; data.report_id = report->id;
if ( wound1 && !alreadyDone[unit1->id][unit2->id] ) { if ( wound1 && already_done.find(std::make_pair(unit1->id,unit2->id)) == already_done.end() ) {
data.attacker = unit1->id; data.attacker = unit1->id;
data.defender = unit2->id; data.defender = unit2->id;
data.wound = wound1->id; data.wound = wound1->id;
alreadyDone[data.attacker][data.defender] = 1; already_done.emplace(unit1->id, unit2->id);
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for unit1 attack unit attack event\n"); DEBUG(log,out).print("calling handler for unit1 attack unit attack event\n");
handle.eventHandler(out, (void*)&data); handle.eventHandler(out, (void*)&data);
} }
} }
if ( wound2 && !alreadyDone[unit1->id][unit2->id] ) { if ( wound2 && already_done.find(std::make_pair(unit1->id,unit2->id)) == already_done.end() ) {
data.attacker = unit2->id; data.attacker = unit2->id;
data.defender = unit1->id; data.defender = unit1->id;
data.wound = wound2->id; data.wound = wound2->id;
alreadyDone[data.attacker][data.defender] = 1; already_done.emplace(unit1->id, unit2->id);
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for unit2 attack unit attack event\n"); DEBUG(log,out).print("calling handler for unit2 attack unit attack event\n");
handle.eventHandler(out, (void*)&data); handle.eventHandler(out, (void*)&data);
} }
@ -1041,9 +1105,9 @@ static void manageUnitAttackEvent(color_ostream& out) {
data.attacker = unit2->id; data.attacker = unit2->id;
data.defender = unit1->id; data.defender = unit1->id;
data.wound = -1; data.wound = -1;
alreadyDone[data.attacker][data.defender] = 1;
for (auto &key_value : copy) { already_done.emplace(unit1->id, unit2->id);
EventHandler &handle = key_value.second; for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit1 killed unit attack event\n"); DEBUG(log,out).print("calling handler for unit1 killed unit attack event\n");
handle.eventHandler(out, (void*)&data); handle.eventHandler(out, (void*)&data);
} }
@ -1053,9 +1117,9 @@ static void manageUnitAttackEvent(color_ostream& out) {
data.attacker = unit1->id; data.attacker = unit1->id;
data.defender = unit2->id; data.defender = unit2->id;
data.wound = -1; data.wound = -1;
alreadyDone[data.attacker][data.defender] = 1;
for (auto &key_value : copy) { already_done.emplace(unit1->id, unit2->id);
EventHandler &handle = key_value.second; for (auto &[_,handle] : copy) {
DEBUG(log,out).print("calling handler for unit2 killed unit attack event\n"); DEBUG(log,out).print("calling handler for unit2 killed unit attack event\n");
handle.eventHandler(out, (void*)&data); handle.eventHandler(out, (void*)&data);
} }
@ -1313,8 +1377,7 @@ static void manageInteractionEvent(color_ostream& out) {
lastAttacker = df::unit::find(data.attacker); lastAttacker = df::unit::find(data.attacker);
//lastDefender = df::unit::find(data.defender); //lastDefender = df::unit::find(data.defender);
//fire event //fire event
for (auto &key_value : copy) { for (auto &[_,handle] : copy) {
EventHandler &handle = key_value.second;
DEBUG(log,out).print("calling handler for interaction event\n"); DEBUG(log,out).print("calling handler for interaction event\n");
handle.eventHandler(out, (void*)&data); handle.eventHandler(out, (void*)&data);
} }

@ -87,6 +87,7 @@ using namespace DFHack;
#include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_legendsst.h" #include "df/viewscreen_legendsst.h"
#include "df/viewscreen_new_regionst.h" #include "df/viewscreen_new_regionst.h"
#include "df/viewscreen_setupdwarfgamest.h"
#include "df/viewscreen_titlest.h" #include "df/viewscreen_titlest.h"
#include "df/world.h" #include "df/world.h"
@ -174,6 +175,45 @@ DEFINE_GET_FOCUS_STRING_HANDLER(new_region)
focusStrings.push_back(baseFocus); focusStrings.push_back(baseFocus);
} }
DEFINE_GET_FOCUS_STRING_HANDLER(setupdwarfgame)
{
if (screen->doing_custom_settings)
focusStrings.push_back(baseFocus + "/CustomSettings");
else if (game->main_interface.options.open)
focusStrings.push_back(baseFocus + "/Abort");
else if (screen->initial_selection == 1)
focusStrings.push_back(baseFocus + "/Default");
else if (game->main_interface.name_creator.open) {
switch (game->main_interface.name_creator.context) {
case df::name_creator_context_type::EMBARK_FORT_NAME:
focusStrings.push_back(baseFocus + "/FortName");
break;
case df::name_creator_context_type::EMBARK_GROUP_NAME:
focusStrings.push_back(baseFocus + "/GroupName");
break;
default:
break;
}
}
else if (game->main_interface.image_creator.open) {
focusStrings.push_back(baseFocus + "/GroupSymbol");
}
else if (screen->viewing_objections != 0)
focusStrings.push_back(baseFocus + "/Objections");
else {
switch (screen->mode) {
case 0: focusStrings.push_back(baseFocus + "/Dwarves"); break;
case 1: focusStrings.push_back(baseFocus + "/Items"); break;
case 2: focusStrings.push_back(baseFocus + "/Animals"); break;
default:
break;
}
}
if (focusStrings.empty())
focusStrings.push_back(baseFocus + "/Default");
}
DEFINE_GET_FOCUS_STRING_HANDLER(legends) DEFINE_GET_FOCUS_STRING_HANDLER(legends)
{ {
if (screen->init_stage != -1) if (screen->init_stage != -1)

@ -956,6 +956,18 @@ int dfhack_lua_viewscreen::do_notify(lua_State *L)
return 1; return 1;
} }
void dfhack_lua_viewscreen::markInputAsHandled() {
if (!enabler)
return;
// clear text buffer
enabler->last_text_input[0] = '\0';
// mark clicked mouse buttons as handled
enabler->mouse_lbut = 0;
enabler->mouse_rbut = 0;
}
int dfhack_lua_viewscreen::do_input(lua_State *L) int dfhack_lua_viewscreen::do_input(lua_State *L)
{ {
auto self = get_self(L); auto self = get_self(L);
@ -977,7 +989,11 @@ int dfhack_lua_viewscreen::do_input(lua_State *L)
lua_pushvalue(L, -2); lua_pushvalue(L, -2);
Lua::PushInterfaceKeys(L, Screen::normalize_text_keys(*keys)); Lua::PushInterfaceKeys(L, Screen::normalize_text_keys(*keys));
lua_call(L, 2, 0); lua_call(L, 2, 1);
if (lua_toboolean(L, -1))
markInputAsHandled();
lua_pop(L, 1);
self->update_focus(L, -1); self->update_focus(L, -1);
return 0; return 0;
} }
@ -1004,6 +1020,8 @@ dfhack_lua_viewscreen::~dfhack_lua_viewscreen()
void dfhack_lua_viewscreen::render() void dfhack_lua_viewscreen::render()
{ {
using df::global::enabler;
if (Screen::isDismissed(this)) if (Screen::isDismissed(this))
{ {
if (parent) if (parent)
@ -1011,6 +1029,14 @@ void dfhack_lua_viewscreen::render()
return; return;
} }
if (enabler &&
(enabler->mouse_lbut_down || enabler->mouse_rbut_down || enabler->mouse_mbut_down))
{
// synthesize feed events for held mouse buttons
std::set<df::interface_key> keys;
feed(&keys);
}
dfhack_viewscreen::render(); dfhack_viewscreen::render();
safe_call_lua(do_render, 0, 0); safe_call_lua(do_render, 0, 0);

@ -1,3 +1,5 @@
#include <algorithm>
#include <atomic>
#include <mutex> #include <mutex>
#include <numeric> #include <numeric>
#include <unordered_map> #include <unordered_map>
@ -28,9 +30,35 @@ namespace DFHack {
DBG_DECLARE(core, textures, DebugCategory::LINFO); DBG_DECLARE(core, textures, DebugCategory::LINFO);
} }
struct ReservedRange {
void init(int32_t start) {
this->start = start;
this->end = start + ReservedRange::size;
this->current = start;
this->is_installed = true;
}
long get_new_texpos() {
if (this->current == this->end)
return -1;
return this->current++;
}
static const int32_t size = 10000; // size of reserved texpos buffer
int32_t start = -1;
int32_t end = -1;
long current = -1;
bool is_installed = false;
};
static ReservedRange reserved_range{};
static std::unordered_map<TexposHandle, long> g_handle_to_texpos; static std::unordered_map<TexposHandle, long> g_handle_to_texpos;
static std::unordered_map<TexposHandle, long> g_handle_to_reserved_texpos;
static std::unordered_map<TexposHandle, SDL_Surface*> g_handle_to_surface; static std::unordered_map<TexposHandle, SDL_Surface*> g_handle_to_surface;
static std::unordered_map<std::string, std::vector<TexposHandle>> g_tileset_to_handles;
static std::vector<TexposHandle> g_delayed_regs;
static std::mutex g_adding_mutex; static std::mutex g_adding_mutex;
static std::atomic<bool> loading_state = false;
static SDL_Surface* dummy_surface = NULL;
// Converts an arbitrary Surface to something like the display format // Converts an arbitrary Surface to something like the display format
// (32-bit RGBA), and converts magenta to transparency if convert_magenta is set // (32-bit RGBA), and converts magenta to transparency if convert_magenta is set
@ -71,6 +99,12 @@ static long add_texture(SDL_Surface* surface) {
return texpos; return texpos;
} }
// register surface in texture raws to specific texpos
static void insert_texture(SDL_Surface* surface, long texpos) {
std::lock_guard<std::mutex> lg_add_texture(g_adding_mutex);
enabler->textures.raws[texpos] = surface;
}
// delete surface from texture raws // delete surface from texture raws
static void delete_texture(long texpos) { static void delete_texture(long texpos) {
std::lock_guard<std::mutex> lg_add_texture(g_adding_mutex); std::lock_guard<std::mutex> lg_add_texture(g_adding_mutex);
@ -94,7 +128,8 @@ SDL_Surface* create_texture(std::vector<uint32_t>& pixels, int texture_px_w, int
// convert single surface into tiles according w/h // convert single surface into tiles according w/h
// register tiles in texture raws and return handles // register tiles in texture raws and return handles
std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int tile_px_h) { std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int tile_px_h,
bool reserved) {
std::vector<TexposHandle> handles{}; std::vector<TexposHandle> handles{};
if (!surface) if (!surface)
return handles; return handles;
@ -102,6 +137,12 @@ std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int
int dimx = surface->w / tile_px_w; int dimx = surface->w / tile_px_w;
int dimy = surface->h / tile_px_h; int dimy = surface->h / tile_px_h;
if (reserved && (dimx * dimy > reserved_range.end - reserved_range.current)) {
WARN(textures).print(
"there is not enough space in reserved range for whole tileset, using dynamic range\n");
reserved = false;
}
for (int y = 0; y < dimy; y++) { for (int y = 0; y < dimy; y++) {
for (int x = 0; x < dimx; x++) { for (int x = 0; x < dimx; x++) {
SDL_Surface* tile = DFSDL_CreateRGBSurface( SDL_Surface* tile = DFSDL_CreateRGBSurface(
@ -109,7 +150,7 @@ std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int
surface->format->Bmask, surface->format->Amask); surface->format->Bmask, surface->format->Amask);
SDL_Rect vp{tile_px_w * x, tile_px_h * y, tile_px_w, tile_px_h}; SDL_Rect vp{tile_px_w * x, tile_px_h * y, tile_px_w, tile_px_h};
DFSDL_UpperBlit(surface, &vp, tile, NULL); DFSDL_UpperBlit(surface, &vp, tile, NULL);
auto handle = Textures::loadTexture(tile); auto handle = Textures::loadTexture(tile, reserved);
handles.push_back(handle); handles.push_back(handle);
} }
} }
@ -118,20 +159,46 @@ std::vector<TexposHandle> slice_tileset(SDL_Surface* surface, int tile_px_w, int
return handles; return handles;
} }
TexposHandle Textures::loadTexture(SDL_Surface* surface) { TexposHandle Textures::loadTexture(SDL_Surface* surface, bool reserved) {
if (!surface || !enabler) if (!surface || !enabler)
return 0; // should be some error, i guess return 0; // should be some error, i guess
if (loading_state)
reserved = true; // use reserved range during loading for all textures
auto handle = reinterpret_cast<uintptr_t>(surface); auto handle = reinterpret_cast<uintptr_t>(surface);
g_handle_to_surface.emplace(handle, surface); g_handle_to_surface.emplace(handle, surface);
surface->refcount++; // prevent destruct on next FreeSurface by game surface->refcount++; // prevent destruct on next FreeSurface by game
if (reserved && reserved_range.is_installed) {
auto texpos = reserved_range.get_new_texpos();
if (texpos != -1) {
insert_texture(surface, texpos);
g_handle_to_reserved_texpos.emplace(handle, texpos);
dummy_surface->refcount--;
return handle;
}
if (loading_state) { // if we in loading state and reserved range is full -> error
ERR(textures).printerr("reserved range limit has been reached, use dynamic range\n");
return 0;
}
}
// if we here in loading state = true, then it should be dynamic range -> delay reg
if (loading_state) {
g_delayed_regs.push_back(handle);
} else {
auto texpos = add_texture(surface); auto texpos = add_texture(surface);
g_handle_to_texpos.emplace(handle, texpos); g_handle_to_texpos.emplace(handle, texpos);
}
return handle; return handle;
} }
std::vector<TexposHandle> Textures::loadTileset(const std::string& file, int tile_px_w, std::vector<TexposHandle> Textures::loadTileset(const std::string& file, int tile_px_w,
int tile_px_h) { int tile_px_h, bool reserved) {
if (g_tileset_to_handles.contains(file))
return g_tileset_to_handles[file];
if (!enabler) if (!enabler)
return std::vector<TexposHandle>{}; return std::vector<TexposHandle>{};
@ -142,9 +209,10 @@ std::vector<TexposHandle> Textures::loadTileset(const std::string& file, int til
} }
surface = canonicalize_format(surface); surface = canonicalize_format(surface);
auto handles = slice_tileset(surface, tile_px_w, tile_px_h); auto handles = slice_tileset(surface, tile_px_w, tile_px_h, reserved);
DEBUG(textures).print("loaded %zd textures from '%s'\n", handles.size(), file.c_str()); DEBUG(textures).print("loaded %zd textures from '%s'\n", handles.size(), file.c_str());
g_tileset_to_handles[file] = handles;
return handles; return handles;
} }
@ -153,11 +221,18 @@ long Textures::getTexposByHandle(TexposHandle handle) {
if (!handle || !enabler) if (!handle || !enabler)
return -1; return -1;
if (g_handle_to_reserved_texpos.contains(handle))
return g_handle_to_reserved_texpos[handle];
if (g_handle_to_texpos.contains(handle)) if (g_handle_to_texpos.contains(handle))
return g_handle_to_texpos[handle]; return g_handle_to_texpos[handle];
if (std::find(g_delayed_regs.begin(), g_delayed_regs.end(), handle) != g_delayed_regs.end())
return 0;
if (g_handle_to_surface.contains(handle)) { if (g_handle_to_surface.contains(handle)) {
g_handle_to_surface[handle]->refcount++; // prevent destruct on next FreeSurface by game g_handle_to_surface[handle]->refcount++; // prevent destruct on next FreeSurface by game
if (loading_state) { // reinit dor dynamic range during loading -> delayed
g_delayed_regs.push_back(handle);
return 0;
}
auto texpos = add_texture(g_handle_to_surface[handle]); auto texpos = add_texture(g_handle_to_surface[handle]);
g_handle_to_texpos.emplace(handle, texpos); g_handle_to_texpos.emplace(handle, texpos);
return texpos; return texpos;
@ -166,22 +241,24 @@ long Textures::getTexposByHandle(TexposHandle handle) {
return -1; return -1;
} }
TexposHandle Textures::createTile(std::vector<uint32_t>& pixels, int tile_px_w, int tile_px_h) { TexposHandle Textures::createTile(std::vector<uint32_t>& pixels, int tile_px_w, int tile_px_h,
bool reserved) {
if (!enabler) if (!enabler)
return 0; return 0;
auto texture = create_texture(pixels, tile_px_w, tile_px_h); auto texture = create_texture(pixels, tile_px_w, tile_px_h);
auto handle = Textures::loadTexture(texture); auto handle = Textures::loadTexture(texture, reserved);
return handle; return handle;
} }
std::vector<TexposHandle> Textures::createTileset(std::vector<uint32_t>& pixels, int texture_px_w, std::vector<TexposHandle> Textures::createTileset(std::vector<uint32_t>& pixels, int texture_px_w,
int texture_px_h, int tile_px_w, int tile_px_h) { int texture_px_h, int tile_px_w, int tile_px_h,
bool reserved) {
if (!enabler) if (!enabler)
return std::vector<TexposHandle>{}; return std::vector<TexposHandle>{};
auto texture = create_texture(pixels, texture_px_w, texture_px_h); auto texture = create_texture(pixels, texture_px_w, texture_px_h);
auto handles = slice_tileset(texture, tile_px_w, tile_px_h); auto handles = slice_tileset(texture, tile_px_w, tile_px_h, reserved);
return handles; return handles;
} }
@ -192,8 +269,13 @@ void Textures::deleteHandle(TexposHandle handle) {
auto texpos = Textures::getTexposByHandle(handle); auto texpos = Textures::getTexposByHandle(handle);
if (texpos > 0) if (texpos > 0)
delete_texture(texpos); delete_texture(texpos);
if (g_handle_to_reserved_texpos.contains(handle))
g_handle_to_reserved_texpos.erase(handle);
if (g_handle_to_texpos.contains(handle)) if (g_handle_to_texpos.contains(handle))
g_handle_to_texpos.erase(handle); g_handle_to_texpos.erase(handle);
if (auto it = std::find(g_delayed_regs.begin(), g_delayed_regs.end(), handle);
it != g_delayed_regs.end())
g_delayed_regs.erase(it);
if (g_handle_to_surface.contains(handle)) { if (g_handle_to_surface.contains(handle)) {
auto surface = g_handle_to_surface[handle]; auto surface = g_handle_to_surface[handle];
while (surface->refcount) while (surface->refcount)
@ -207,20 +289,45 @@ static void reset_texpos() {
g_handle_to_texpos.clear(); g_handle_to_texpos.clear();
} }
static void reset_reserved_texpos() {
DEBUG(textures).print("resetting reserved texture mappings\n");
g_handle_to_reserved_texpos.clear();
}
static void reset_tilesets() {
DEBUG(textures).print("resetting tileset to handle mappings\n");
g_tileset_to_handles.clear();
}
static void reset_surface() { static void reset_surface() {
DEBUG(textures).print("deleting cached surfaces\n");
for (auto& entry : g_handle_to_surface) { for (auto& entry : g_handle_to_surface) {
DFSDL_FreeSurface(entry.second); DFSDL_FreeSurface(entry.second);
} }
g_handle_to_surface.clear(); g_handle_to_surface.clear();
} }
static void register_delayed_handles() {
DEBUG(textures).print("register delayed handles, size %zd\n", g_delayed_regs.size());
for (auto& handle : g_delayed_regs) {
auto texpos = add_texture(g_handle_to_surface[handle]);
g_handle_to_texpos.emplace(handle, texpos);
}
g_delayed_regs.clear();
}
// reset point on New Game // reset point on New Game
struct tracking_stage_new_region : df::viewscreen_new_regionst { struct tracking_stage_new_region : df::viewscreen_new_regionst {
typedef df::viewscreen_new_regionst interpose_base; typedef df::viewscreen_new_regionst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
if (this->m_raw_load_stage != this->raw_load_stage) { if (this->m_raw_load_stage != this->raw_load_stage) {
TRACE(textures).print("raw_load_stage %d -> %d\n", this->m_raw_load_stage, this->raw_load_stage); TRACE(textures).print("raw_load_stage %d -> %d\n", this->m_raw_load_stage,
this->raw_load_stage);
bool tmp_state = loading_state;
loading_state = this->raw_load_stage >= 0 && this->raw_load_stage < 3 ? true : false;
if (tmp_state != loading_state && !loading_state)
register_delayed_handles();
this->m_raw_load_stage = this->raw_load_stage; this->m_raw_load_stage = this->raw_load_stage;
if (this->m_raw_load_stage == 1) if (this->m_raw_load_stage == 1)
reset_texpos(); reset_texpos();
@ -240,6 +347,10 @@ struct tracking_stage_adopt_region : df::viewscreen_adopt_regionst {
DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
if (this->m_cur_step != this->cur_step) { if (this->m_cur_step != this->cur_step) {
TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step); TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step);
bool tmp_state = loading_state;
loading_state = this->cur_step >= 0 && this->cur_step < 3 ? true : false;
if (tmp_state != loading_state && !loading_state)
register_delayed_handles();
this->m_cur_step = this->cur_step; this->m_cur_step = this->cur_step;
if (this->m_cur_step == 1) if (this->m_cur_step == 1)
reset_texpos(); reset_texpos();
@ -259,6 +370,10 @@ struct tracking_stage_load_region : df::viewscreen_loadgamest {
DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
if (this->m_cur_step != this->cur_step) { if (this->m_cur_step != this->cur_step) {
TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step); TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step);
bool tmp_state = loading_state;
loading_state = this->cur_step >= 0 && this->cur_step < 3 ? true : false;
if (tmp_state != loading_state && !loading_state)
register_delayed_handles();
this->m_cur_step = this->cur_step; this->m_cur_step = this->cur_step;
if (this->m_cur_step == 1) if (this->m_cur_step == 1)
reset_texpos(); reset_texpos();
@ -278,6 +393,10 @@ struct tracking_stage_new_arena : df::viewscreen_new_arenast {
DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) {
if (this->m_cur_step != this->cur_step) { if (this->m_cur_step != this->cur_step) {
TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step); TRACE(textures).print("step %d -> %d\n", this->m_cur_step, this->cur_step);
bool tmp_state = loading_state;
loading_state = this->cur_step >= 0 && this->cur_step < 3 ? true : false;
if (tmp_state != loading_state && !loading_state)
register_delayed_handles();
this->m_cur_step = this->cur_step; this->m_cur_step = this->cur_step;
if (this->m_cur_step == 0) if (this->m_cur_step == 0)
reset_texpos(); reset_texpos();
@ -304,12 +423,31 @@ static void uninstall_reset_point() {
INTERPOSE_HOOK(tracking_stage_new_arena, logic).remove(); INTERPOSE_HOOK(tracking_stage_new_arena, logic).remove();
} }
static void reserve_static_range() {
if (static_cast<size_t>(enabler->textures.init_texture_size) != enabler->textures.raws.size()) {
WARN(textures).print(
"reserved range can't be installed! all textures will be loaded to dynamic range!");
return;
}
reserved_range.init(enabler->textures.init_texture_size);
dummy_surface =
DFSDL_CreateRGBSurfaceWithFormat(0, 0, 0, 32, SDL_PixelFormatEnum::SDL_PIXELFORMAT_RGBA32);
dummy_surface->refcount += ReservedRange::size;
for (int32_t i = 0; i < ReservedRange::size; i++) {
add_texture(dummy_surface);
}
enabler->textures.init_texture_size += ReservedRange::size;
}
void Textures::init(color_ostream& out) { void Textures::init(color_ostream& out) {
if (!enabler) if (!enabler)
return; return;
reserve_static_range();
install_reset_point(); install_reset_point();
DEBUG(textures, out).print("dynamic texture loading ready"); DEBUG(textures, out)
.print("dynamic texture loading ready, reserved range %d-%d\n", reserved_range.start,
reserved_range.end);
} }
void Textures::cleanup() { void Textures::cleanup() {
@ -317,6 +455,8 @@ void Textures::cleanup() {
return; return;
reset_texpos(); reset_texpos();
reset_reserved_texpos();
reset_tilesets();
reset_surface(); reset_surface();
uninstall_reset_point(); uninstall_reset_point();
} }

@ -131,6 +131,37 @@ void Translation::setNickname(df::language_name *name, std::string nick)
} }
} }
static string translate_word(const df::language_name * name, size_t word_idx) {
CHECK_NULL_POINTER(name);
auto translation = vector_get(world->raws.language.translations, name->language);
if (!translation)
return "";
auto word = vector_get(translation->words, word_idx);
if (!word)
return "";
return *word;
}
static string translate_english_word(const df::language_name * name, size_t part_idx) {
CHECK_NULL_POINTER(name);
if (part_idx >= 7)
return "";
auto words = vector_get(world->raws.language.words, name->words[part_idx]);
if (!words)
return "";
df::part_of_speech part = name->parts_of_speech[part_idx];
if (part < df::part_of_speech::Noun || part > df::part_of_speech::VerbGerund)
return "";
return words->forms[part];
}
string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart) string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart)
{ {
CHECK_NULL_POINTER(name); CHECK_NULL_POINTER(name);
@ -166,20 +197,20 @@ string Translation::TranslateName(const df::language_name * name, bool inEnglish
{ {
word.clear(); word.clear();
if (name->words[0] >= 0) if (name->words[0] >= 0)
word.append(*world->raws.language.translations[name->language]->words[name->words[0]]); word.append(translate_word(name, name->words[0]));
if (name->words[1] >= 0) if (name->words[1] >= 0)
word.append(*world->raws.language.translations[name->language]->words[name->words[1]]); word.append(translate_word(name, name->words[1]));
addNameWord(out, word); addNameWord(out, word);
} }
word.clear(); word.clear();
for (int i = 2; i <= 5; i++) for (int i = 2; i <= 5; i++)
if (name->words[i] >= 0) if (name->words[i] >= 0)
word.append(*world->raws.language.translations[name->language]->words[name->words[i]]); word.append(translate_word(name, name->words[i]));
addNameWord(out, word); addNameWord(out, word);
if (name->words[6] >= 0) if (name->words[6] >= 0)
{ {
word.clear(); word.clear();
word.append(*world->raws.language.translations[name->language]->words[name->words[6]]); word.append(translate_word(name, name->words[6]));
addNameWord(out, word); addNameWord(out, word);
} }
} }
@ -189,9 +220,9 @@ string Translation::TranslateName(const df::language_name * name, bool inEnglish
{ {
word.clear(); word.clear();
if (name->words[0] >= 0) if (name->words[0] >= 0)
word.append(world->raws.language.words[name->words[0]]->forms[name->parts_of_speech[0]]); word.append(translate_english_word(name, 0));
if (name->words[1] >= 0) if (name->words[1] >= 0)
word.append(world->raws.language.words[name->words[1]]->forms[name->parts_of_speech[1]]); word.append(translate_english_word(name, 1));
addNameWord(out, word); addNameWord(out, word);
} }
if (name->words[2] >= 0 || name->words[3] >= 0 || name->words[4] >= 0 || name->words[5] >= 0) if (name->words[2] >= 0 || name->words[3] >= 0 || name->words[4] >= 0 || name->words[5] >= 0)
@ -201,10 +232,10 @@ string Translation::TranslateName(const df::language_name * name, bool inEnglish
else else
out.append("The"); out.append("The");
} }
for (int i = 2; i <= 5; i++) for (size_t i = 2; i <= 5; i++)
{ {
if (name->words[i] >= 0) if (name->words[i] >= 0)
addNameWord(out, world->raws.language.words[name->words[i]]->forms[name->parts_of_speech[i]]); addNameWord(out, translate_english_word(name, i));
} }
if (name->words[6] >= 0) if (name->words[6] >= 0)
{ {
@ -213,7 +244,7 @@ string Translation::TranslateName(const df::language_name * name, bool inEnglish
else else
out.append("Of"); out.append("Of");
addNameWord(out, world->raws.language.words[name->words[6]]->forms[name->parts_of_speech[6]]); addNameWord(out, translate_english_word(name, 6));
} }
} }

@ -1 +1 @@
Subproject commit 041493b221e0799c106abeac1f86df4535ab80d3 Subproject commit f38f3c4955d604f2b5a8e0d952e676a0ab05c053

@ -167,7 +167,7 @@ if(BUILD_SUPPORTED)
dfhack_plugin(tiletypes tiletypes.cpp Brushes.h LINK_LIBRARIES lua) dfhack_plugin(tiletypes tiletypes.cpp Brushes.h LINK_LIBRARIES lua)
#dfhack_plugin(title-folder title-folder.cpp) #dfhack_plugin(title-folder title-folder.cpp)
#dfhack_plugin(trackstop trackstop.cpp) #dfhack_plugin(trackstop trackstop.cpp)
#dfhack_plugin(tubefill tubefill.cpp) dfhack_plugin(tubefill tubefill.cpp)
#add_subdirectory(tweak) #add_subdirectory(tweak)
dfhack_plugin(workflow workflow.cpp LINK_LIBRARIES lua) dfhack_plugin(workflow workflow.cpp LINK_LIBRARIES lua)
dfhack_plugin(work-now work-now.cpp) dfhack_plugin(work-now work-now.cpp)

@ -305,6 +305,7 @@ static void cleanup_state()
{ {
enable_autolabor = false; enable_autolabor = false;
labor_infos.clear(); labor_infos.clear();
game->external_flag &= ~1; // reinstate DF's work detail system
} }
static void reset_labor(df::unit_labor labor) static void reset_labor(df::unit_labor labor)
@ -326,6 +327,8 @@ static void init_state()
if (!enable_autolabor) if (!enable_autolabor)
return; return;
game->external_flag |= 1; // bypass DF's work detail system
auto cfg_haulpct = World::GetPersistentData("autolabor/haulpct"); auto cfg_haulpct = World::GetPersistentData("autolabor/haulpct");
if (cfg_haulpct.isValid()) if (cfg_haulpct.isValid())
{ {
@ -413,8 +416,17 @@ static void enable_plugin(color_ostream &out)
cleanup_state(); cleanup_state();
init_state(); init_state();
}
static void disable_plugin(color_ostream& out)
{
if (config.isValid())
setOptionEnabled(CF_ENABLED, false);
enable_autolabor = false;
out << "Disabling autolabor." << std::endl;
game->external_flag |= 1; // shut down DF's work detail system cleanup_state();
} }
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
@ -1081,12 +1093,7 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable )
} }
else if(!enable && enable_autolabor) else if(!enable && enable_autolabor)
{ {
enable_autolabor = false; disable_plugin(out);
setOptionEnabled(CF_ENABLED, false);
game->external_flag &= ~1; // reenable DF's work detail system
out << "Autolabor is disabled." << std::endl;
} }
return CR_OK; return CR_OK;

@ -151,24 +151,6 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
return CR_OK; return CR_OK;
} }
// Name functions taken from manipulator.cpp
static std::string get_first_name(df::unit *unit)
{
return Translation::capitalize(unit->name.first_name);
}
static std::string get_last_name(df::unit *unit)
{
df::language_name name = unit->name;
std::string ret = "";
for (int i = 0; i < 2; i++)
{
if (name.words[i] >= 0)
ret += *world->raws.language.translations[name.language]->words[name.words[i]];
}
return Translation::capitalize(ret);
}
// Queue up a single order to engrave the slab for the given unit // Queue up a single order to engrave the slab for the given unit
static void createSlabJob(df::unit *unit) static void createSlabJob(df::unit *unit)
{ {
@ -212,7 +194,7 @@ static void checkslabs(color_ostream &out)
) )
{ {
createSlabJob(ghost); createSlabJob(ghost);
auto fullName = get_first_name(ghost) + " " + get_last_name(ghost); auto fullName = Translation::TranslateName(&ghost->name, false);
out.print("Added slab order for ghost %s\n", fullName.c_str()); out.print("Added slab order for ghost %s\n", fullName.c_str());
} }
} }

@ -4,6 +4,7 @@
#include <stack> #include <stack>
#include <string> #include <string>
#include <cmath> #include <cmath>
#include <memory>
#include "Core.h" #include "Core.h"
#include "Console.h" #include "Console.h"
@ -1072,14 +1073,13 @@ command_result digv (color_ostream &out, vector <string> & parameters)
con.printerr("I won't dig the borders. That would be cheating!\n"); con.printerr("I won't dig the borders. That would be cheating!\n");
return CR_FAILURE; return CR_FAILURE;
} }
MapExtras::MapCache * MCache = new MapExtras::MapCache; std::unique_ptr<MapExtras::MapCache> MCache = std::make_unique<MapExtras::MapCache>();
df::tile_designation des = MCache->designationAt(xy); df::tile_designation des = MCache->designationAt(xy);
df::tiletype tt = MCache->tiletypeAt(xy); df::tiletype tt = MCache->tiletypeAt(xy);
int16_t veinmat = MCache->veinMaterialAt(xy); int16_t veinmat = MCache->veinMaterialAt(xy);
if( veinmat == -1 ) if( veinmat == -1 )
{ {
con.printerr("This tile is not a vein.\n"); con.printerr("This tile is not a vein.\n");
delete MCache;
return CR_FAILURE; return CR_FAILURE;
} }
con.print("%d/%d/%d tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, des.whole); con.print("%d/%d/%d tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, des.whole);
@ -1192,7 +1192,6 @@ command_result digv (color_ostream &out, vector <string> & parameters)
} }
} }
MCache->WriteAll(); MCache->WriteAll();
delete MCache;
return CR_OK; return CR_OK;
} }
@ -1259,7 +1258,7 @@ command_result digl (color_ostream &out, vector <string> & parameters)
con.printerr("I won't dig the borders. That would be cheating!\n"); con.printerr("I won't dig the borders. That would be cheating!\n");
return CR_FAILURE; return CR_FAILURE;
} }
MapExtras::MapCache * MCache = new MapExtras::MapCache; std::unique_ptr<MapExtras::MapCache> MCache = std::make_unique<MapExtras::MapCache>();
df::tile_designation des = MCache->designationAt(xy); df::tile_designation des = MCache->designationAt(xy);
df::tiletype tt = MCache->tiletypeAt(xy); df::tiletype tt = MCache->tiletypeAt(xy);
int16_t veinmat = MCache->veinMaterialAt(xy); int16_t veinmat = MCache->veinMaterialAt(xy);
@ -1267,7 +1266,6 @@ command_result digl (color_ostream &out, vector <string> & parameters)
if( veinmat != -1 ) if( veinmat != -1 )
{ {
con.printerr("This is a vein. Use digv instead!\n"); con.printerr("This is a vein. Use digv instead!\n");
delete MCache;
return CR_FAILURE; return CR_FAILURE;
} }
con.print("%d/%d/%d tiletype: %d, basemat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, basemat, des.whole); con.print("%d/%d/%d tiletype: %d, basemat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, basemat, des.whole);
@ -1408,7 +1406,6 @@ command_result digl (color_ostream &out, vector <string> & parameters)
} }
} }
MCache->WriteAll(); MCache->WriteAll();
delete MCache;
return CR_OK; return CR_OK;
} }
@ -1424,9 +1421,13 @@ command_result digtype (color_ostream &out, vector <string> & parameters)
return CR_FAILURE; return CR_FAILURE;
} }
uint32_t zMin = 0;
uint32_t xMax,yMax,zMax; uint32_t xMax,yMax,zMax;
Maps::getSize(xMax,yMax,zMax); Maps::getSize(xMax,yMax,zMax);
bool hidden = false;
bool automine = true;
int32_t targetDigType = -1; int32_t targetDigType = -1;
for (string parameter : parameters) { for (string parameter : parameters) {
if ( parameter == "clear" ) if ( parameter == "clear" )
@ -1443,8 +1444,16 @@ command_result digtype (color_ostream &out, vector <string> & parameters)
targetDigType = tile_dig_designation::DownStair; targetDigType = tile_dig_designation::DownStair;
else if ( parameter == "up" ) else if ( parameter == "up" )
targetDigType = tile_dig_designation::UpStair; targetDigType = tile_dig_designation::UpStair;
else if ( parameter == "-z" ) else if ( parameter == "-z" || parameter == "--cur-zlevel" )
{zMax = *window_z + 1; zMin = *window_z;}
else if ( parameter == "--zdown" || parameter == "-d")
zMax = *window_z + 1; zMax = *window_z + 1;
else if ( parameter == "--zup" || parameter == "-u")
zMin = *window_z;
else if ( parameter == "--hidden" || parameter == "-h")
hidden = true;
else if ( parameter == "--no-auto" || parameter == "-a" )
automine = false;
else else
{ {
out.printerr("Invalid parameter: '%s'.\n", parameter.c_str()); out.printerr("Invalid parameter: '%s'.\n", parameter.c_str());
@ -1462,14 +1471,21 @@ command_result digtype (color_ostream &out, vector <string> & parameters)
return CR_FAILURE; return CR_FAILURE;
} }
DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz); DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz);
MapExtras::MapCache * mCache = new MapExtras::MapCache; std::unique_ptr<MapExtras::MapCache> mCache = std::make_unique<MapExtras::MapCache>();
df::tile_designation baseDes = mCache->designationAt(xy); df::tile_designation baseDes = mCache->designationAt(xy);
if (baseDes.bits.hidden && !hidden) {
out.printerr("Cursor is pointing at a hidden tile. Point the cursor at a visible tile when using the --hidden option.\n");
return CR_FAILURE;
}
df::tile_occupancy baseOcc = mCache->occupancyAt(xy);
df::tiletype tt = mCache->tiletypeAt(xy); df::tiletype tt = mCache->tiletypeAt(xy);
int16_t veinmat = mCache->veinMaterialAt(xy); int16_t veinmat = mCache->veinMaterialAt(xy);
if( veinmat == -1 ) if( veinmat == -1 )
{ {
out.printerr("This tile is not a vein.\n"); out.printerr("This tile is not a vein.\n");
delete mCache;
return CR_FAILURE; return CR_FAILURE;
} }
out.print("(%d,%d,%d) tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, baseDes.whole); out.print("(%d,%d,%d) tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, baseDes.whole);
@ -1485,8 +1501,12 @@ command_result digtype (color_ostream &out, vector <string> & parameters)
baseDes.bits.dig = tile_dig_designation::Default; baseDes.bits.dig = tile_dig_designation::Default;
} }
} }
// Auto dig only works on default dig designation. Setting dig_auto for any other designation
// prevents dwarves from digging that tile at all.
if (baseDes.bits.dig == tile_dig_designation::Default && automine) baseOcc.bits.dig_auto = true;
else baseOcc.bits.dig_auto = false;
for( uint32_t z = 0; z < zMax; z++ ) for( uint32_t z = zMin; z < zMax; z++ )
{ {
for( uint32_t x = 1; x < tileXMax-1; x++ ) for( uint32_t x = 1; x < tileXMax-1; x++ )
{ {
@ -1506,18 +1526,22 @@ command_result digtype (color_ostream &out, vector <string> & parameters)
if ( !mCache->testCoord(current) ) if ( !mCache->testCoord(current) )
{ {
out.printerr("testCoord failed at (%d,%d,%d)\n", x, y, z); out.printerr("testCoord failed at (%d,%d,%d)\n", x, y, z);
delete mCache;
return CR_FAILURE; return CR_FAILURE;
} }
df::tile_designation designation = mCache->designationAt(current); df::tile_designation designation = mCache->designationAt(current);
if (designation.bits.hidden && !hidden) continue;
df::tile_occupancy occupancy = mCache->occupancyAt(current);
designation.bits.dig = baseDes.bits.dig; designation.bits.dig = baseDes.bits.dig;
occupancy.bits.dig_auto = baseOcc.bits.dig_auto;
mCache->setDesignationAt(current, designation, priority); mCache->setDesignationAt(current, designation, priority);
mCache->setOccupancyAt(current, occupancy);
} }
} }
} }
mCache->WriteAll(); mCache->WriteAll();
delete mCache;
return CR_OK; return CR_OK;
} }

@ -127,9 +127,9 @@ function InspectorOverlay:onInput(keys)
if not require('plugins.buildingplan').isPlannedBuilding(dfhack.gui.getSelectedBuilding(true)) then if not require('plugins.buildingplan').isPlannedBuilding(dfhack.gui.getSelectedBuilding(true)) then
return false return false
end end
if keys._MOUSE_L_DOWN and mouse_is_over_resume_button(self.frame_parent_rect) then if keys._MOUSE_L and mouse_is_over_resume_button(self.frame_parent_rect) then
return true return true
elseif keys._MOUSE_L_DOWN or keys._MOUSE_R_DOWN or keys.LEAVESCREEN then elseif keys._MOUSE_L or keys._MOUSE_R or keys.LEAVESCREEN then
self:reset() self:reset()
end end
return InspectorOverlay.super.onInput(self, keys) return InspectorOverlay.super.onInput(self, keys)

@ -366,10 +366,10 @@ function ItemSelection:submit(choices)
end end
function ItemSelection:onInput(keys) function ItemSelection:onInput(keys)
if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then if keys.LEAVESCREEN or keys._MOUSE_R then
self.on_cancel() self.on_cancel()
return true return true
elseif keys._MOUSE_L_DOWN then elseif keys._MOUSE_L then
local list = self.subviews.flist.list local list = self.subviews.flist.list
local idx = list:getIdxUnderMouse() local idx = list:getIdxUnderMouse()
if idx then if idx then

@ -272,7 +272,7 @@ function ItemLine:reset()
end end
function ItemLine:onInput(keys) function ItemLine:onInput(keys)
if keys._MOUSE_L_DOWN and self:getMousePos() then if keys._MOUSE_L and self:getMousePos() then
self.on_select(self.idx) self.on_select(self.idx)
end end
return ItemLine.super.onInput(self, keys) return ItemLine.super.onInput(self, keys)
@ -739,7 +739,7 @@ end
function PlannerOverlay:onInput(keys) function PlannerOverlay:onInput(keys)
if not is_plannable() then return false end if not is_plannable() then return false end
if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then if keys.LEAVESCREEN or keys._MOUSE_R then
if uibs.selection_pos:isValid() then if uibs.selection_pos:isValid() then
uibs.selection_pos:clear() uibs.selection_pos:clear()
return true return true
@ -758,7 +758,7 @@ function PlannerOverlay:onInput(keys)
return true return true
end end
if self:is_minimized() then return false end if self:is_minimized() then return false end
if keys._MOUSE_L_DOWN then if keys._MOUSE_L then
if is_over_options_panel() then return false end if is_over_options_panel() then return false end
local detect_rect = copyall(self.frame_rect) local detect_rect = copyall(self.frame_rect)
detect_rect.height = self.subviews.main.frame_rect.height + detect_rect.height = self.subviews.main.frame_rect.height +
@ -828,7 +828,7 @@ function PlannerOverlay:onInput(keys)
end end
end end
end end
return keys._MOUSE_L or keys.SELECT return keys._MOUSE_L_DOWN or keys.SELECT
end end
function PlannerOverlay:render(dc) function PlannerOverlay:render(dc)

@ -5,8 +5,8 @@ local helpdb = require('helpdb')
local overlay = require('plugins.overlay') local overlay = require('plugins.overlay')
local widgets = require('gui.widgets') local widgets = require('gui.widgets')
local logo_textures = dfhack.textures.loadTileset('hack/data/art/logo.png', 8, 12) local logo_textures = dfhack.textures.loadTileset('hack/data/art/logo.png', 8, 12, true)
local logo_hovered_textures = dfhack.textures.loadTileset('hack/data/art/logo_hovered.png', 8, 12) local logo_hovered_textures = dfhack.textures.loadTileset('hack/data/art/logo_hovered.png', 8, 12, true)
local function get_command(cmdline) local function get_command(cmdline)
local first_word = cmdline:trim():split(' +')[1] local first_word = cmdline:trim():split(' +')[1]
@ -65,6 +65,7 @@ function HotspotMenuWidget:init()
tile=function() return dfhack.textures.getTexposByHandle(logo_hovered_textures[idx]) end, tile=function() return dfhack.textures.getTexposByHandle(logo_hovered_textures[idx]) end,
ch=ch, ch=ch,
fg=COLOR_WHITE, fg=COLOR_WHITE,
bold=true,
} }
end end
local function get_tile_token(idx, ch) local function get_tile_token(idx, ch)
@ -78,9 +79,9 @@ function HotspotMenuWidget:init()
self:addviews{ self:addviews{
widgets.Label{ widgets.Label{
text={ text={
get_tile_token(1, '!'), get_tile_token(2, 'D'), get_tile_token(3, 'F'), get_tile_token(4, '!'), NEWLINE, get_tile_token(1, 179), get_tile_token(2, 'D'), get_tile_token(3, 'F'), get_tile_token(4, 179), NEWLINE,
get_tile_token(5, '!'), get_tile_token(6, 'H'), get_tile_token(7, 'a'), get_tile_token(8, '!'), NEWLINE, get_tile_token(5, 179), get_tile_token(6, 'H'), get_tile_token(7, 'a'), get_tile_token(8, 179), NEWLINE,
get_tile_token(9, '!'), get_tile_token(10, 'c'), get_tile_token(11, 'k'), get_tile_token(12, '!'), get_tile_token(9, 179), get_tile_token(10, 'c'), get_tile_token(11, 'k'), get_tile_token(12, 179),
}, },
on_click=function() dfhack.run_command('hotkeys') end, on_click=function() dfhack.run_command('hotkeys') end,
}, },
@ -269,24 +270,22 @@ function Menu:onSubmit2(_, choice)
end end
function Menu:onInput(keys) function Menu:onInput(keys)
if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then if keys.LEAVESCREEN or keys._MOUSE_R then
return false return false
elseif keys.KEYBOARD_CURSOR_RIGHT then elseif keys.KEYBOARD_CURSOR_RIGHT then
self:onSubmit2(self.subviews.list:getSelected()) self:onSubmit2(self.subviews.list:getSelected())
return true return true
elseif keys._MOUSE_L_DOWN then elseif keys._MOUSE_L then
local list = self.subviews.list local list = self.subviews.list
local x = list:getMousePos() local x = list:getMousePos()
if x == 0 then -- clicked on icon if x == 0 then -- clicked on icon
self:onSubmit2(list:getSelected()) self:onSubmit2(list:getSelected())
df.global.enabler.mouse_lbut = 0
return true return true
end end
if not self:getMouseFramePos() then if not self:getMouseFramePos() then
self.parent_view:dismiss() self.parent_view:dismiss()
return true return true
end end
df.global.enabler.mouse_lbut = 0
end end
self:inputToSubviews(keys) self:inputToSubviews(keys)
return true -- we're modal return true -- we're modal

@ -62,12 +62,16 @@ local function do_export()
}:show() }:show()
end end
local function do_recheck()
dfhack.run_command('orders', 'recheck')
end
OrdersOverlay = defclass(OrdersOverlay, overlay.OverlayWidget) OrdersOverlay = defclass(OrdersOverlay, overlay.OverlayWidget)
OrdersOverlay.ATTRS{ OrdersOverlay.ATTRS{
default_pos={x=53,y=-6}, default_pos={x=53,y=-6},
default_enabled=true, default_enabled=true,
viewscreens='dwarfmode/Info/WORK_ORDERS/Default', viewscreens='dwarfmode/Info/WORK_ORDERS/Default',
frame={w=30, h=4}, frame={w=46, h=4},
} }
function OrdersOverlay:init() function OrdersOverlay:init()
@ -95,13 +99,20 @@ function OrdersOverlay:init()
}, },
widgets.HotkeyLabel{ widgets.HotkeyLabel{
frame={t=0, l=15}, frame={t=0, l=15},
label='recheck',
key='CUSTOM_CTRL_K',
auto_width=true,
on_activate=do_recheck,
},
widgets.HotkeyLabel{
frame={t=1, l=15},
label='sort', label='sort',
key='CUSTOM_CTRL_O', key='CUSTOM_CTRL_O',
auto_width=true, auto_width=true,
on_activate=do_sort, on_activate=do_sort,
}, },
widgets.HotkeyLabel{ widgets.HotkeyLabel{
frame={t=1, l=15}, frame={t=0, l=31},
label='clear', label='clear',
key='CUSTOM_CTRL_C', key='CUSTOM_CTRL_C',
auto_width=true, auto_width=true,
@ -157,7 +168,91 @@ function OrdersOverlay:render(dc)
OrdersOverlay.super.render(self, dc) OrdersOverlay.super.render(self, dc)
end end
-- Resets the selected work order to the `Checking` state
local function set_current_inactive()
local scrConditions = df.global.game.main_interface.info.work_orders.conditions
if scrConditions.open then
dfhack.run_command('orders', 'recheck', 'this')
else
qerror("Order conditions is not open")
end
end
local function is_current_active()
local scrConditions = df.global.game.main_interface.info.work_orders.conditions
local order = scrConditions.wq
return order.status.active
end
-- -------------------
-- RecheckOverlay
--
local focusString = 'dwarfmode/Info/WORK_ORDERS/Conditions'
RecheckOverlay = defclass(RecheckOverlay, overlay.OverlayWidget)
RecheckOverlay.ATTRS{
default_pos={x=6,y=8},
default_enabled=true,
viewscreens=focusString,
-- width is the sum of lengths of `[` + `Ctrl+A` + `: ` + button.label + `]`
frame={w=1 + 6 + 2 + 16 + 1, h=3},
}
local function areTabsInTwoRows()
-- get the tile above the order status icon
local pen = dfhack.screen.readTile(7, 7, false)
-- in graphics mode, `0` when one row, something else when two (`67` aka 'C' from "Creatures")
-- in ASCII mode, `32` aka ' ' when one row, something else when two (`196` aka '-' from tab frame's top)
return (pen.ch ~= 0 and pen.ch ~= 32)
end
function RecheckOverlay:updateTextButtonFrame()
local twoRows = areTabsInTwoRows()
if (self._twoRows == twoRows) then return false end
self._twoRows = twoRows
local frame = twoRows
and {b=0, l=0, r=0, h=1}
or {t=0, l=0, r=0, h=1}
self.subviews.button.frame = frame
return true
end
function RecheckOverlay:init()
self:addviews{
widgets.TextButton{
view_id = 'button',
-- frame={t=0, l=0, r=0, h=1}, -- is set in `updateTextButtonFrame()`
label='request re-check',
key='CUSTOM_CTRL_A',
on_activate=set_current_inactive,
enabled=is_current_active,
},
}
self:updateTextButtonFrame()
end
function RecheckOverlay:onRenderBody(dc)
if (self.frame_rect.y1 == 7) then
-- only apply this logic if the overlay is on the same row as
-- originally thought: just above the order status icon
if self:updateTextButtonFrame() then
self:updateLayout()
end
end
RecheckOverlay.super.onRenderBody(self, dc)
end
-- -------------------
OVERLAY_WIDGETS = { OVERLAY_WIDGETS = {
recheck=RecheckOverlay,
overlay=OrdersOverlay, overlay=OrdersOverlay,
} }

@ -506,10 +506,6 @@ function feed_viewscreen_widgets(vs_name, vs, keys)
not _feed_viewscreen_widgets('all', nil, keys) then not _feed_viewscreen_widgets('all', nil, keys) then
return false return false
end end
gui.markMouseClicksHandled(keys)
if keys._MOUSE_L_DOWN then
df.global.enabler.mouse_lbut = 0
end
return true return true
end end

@ -212,7 +212,7 @@ local function melee_skill_effectiveness(unit)
end end
local function get_melee_skill_effectiveness_rating(unit) local function get_melee_skill_effectiveness_rating(unit)
return get_rating(melee_skill_effectiveness(unit), 350000, 2350000, 78, 64, 49, 35) return get_rating(melee_skill_effectiveness(unit), 350000, 2750000, 64, 52, 40, 28)
end end
local function make_sort_by_melee_skill_effectiveness_desc() local function make_sort_by_melee_skill_effectiveness_desc()
@ -272,7 +272,7 @@ local function ranged_skill_effectiveness(unit)
end end
local function get_ranged_skill_effectiveness_rating(unit) local function get_ranged_skill_effectiveness_rating(unit)
return get_rating(ranged_skill_effectiveness(unit), 0, 500000, 90, 62, 44, 27) return get_rating(ranged_skill_effectiveness(unit), 0, 800000, 72, 52, 31, 11)
end end
local function make_sort_by_ranged_skill_effectiveness_desc(list) local function make_sort_by_ranged_skill_effectiveness_desc(list)
@ -345,41 +345,41 @@ end
-- Statistical rating that is higher for dwarves that are mentally stable -- Statistical rating that is higher for dwarves that are mentally stable
local function get_mental_stability(unit) local function get_mental_stability(unit)
local ALTRUISM = unit.status.current_soul.personality.traits.ALTRUISM local altruism = unit.status.current_soul.personality.traits.ALTRUISM
local ANXIETY_PROPENSITY = unit.status.current_soul.personality.traits.ANXIETY_PROPENSITY local anxiety_propensity = unit.status.current_soul.personality.traits.ANXIETY_PROPENSITY
local BRAVERY = unit.status.current_soul.personality.traits.BRAVERY local bravery = unit.status.current_soul.personality.traits.BRAVERY
local CHEER_PROPENSITY = unit.status.current_soul.personality.traits.CHEER_PROPENSITY local cheer_propensity = unit.status.current_soul.personality.traits.CHEER_PROPENSITY
local CURIOUS = unit.status.current_soul.personality.traits.CURIOUS local curious = unit.status.current_soul.personality.traits.CURIOUS
local DISCORD = unit.status.current_soul.personality.traits.DISCORD local discord = unit.status.current_soul.personality.traits.DISCORD
local DUTIFULNESS = unit.status.current_soul.personality.traits.DUTIFULNESS local dutifulness = unit.status.current_soul.personality.traits.DUTIFULNESS
local EMOTIONALLY_OBSESSIVE = unit.status.current_soul.personality.traits.EMOTIONALLY_OBSESSIVE local emotionally_obsessive = unit.status.current_soul.personality.traits.EMOTIONALLY_OBSESSIVE
local HUMOR = unit.status.current_soul.personality.traits.HUMOR local humor = unit.status.current_soul.personality.traits.HUMOR
local LOVE_PROPENSITY = unit.status.current_soul.personality.traits.LOVE_PROPENSITY local love_propensity = unit.status.current_soul.personality.traits.LOVE_PROPENSITY
local PERSEVERENCE = unit.status.current_soul.personality.traits.PERSEVERENCE local perseverence = unit.status.current_soul.personality.traits.PERSEVERENCE
local POLITENESS = unit.status.current_soul.personality.traits.POLITENESS local politeness = unit.status.current_soul.personality.traits.POLITENESS
local PRIVACY = unit.status.current_soul.personality.traits.PRIVACY local privacy = unit.status.current_soul.personality.traits.PRIVACY
local STRESS_VULNERABILITY = unit.status.current_soul.personality.traits.STRESS_VULNERABILITY local stress_vulnerability = unit.status.current_soul.personality.traits.STRESS_VULNERABILITY
local TOLERANT = unit.status.current_soul.personality.traits.TOLERANT local tolerant = unit.status.current_soul.personality.traits.TOLERANT
local CRAFTSMANSHIP = setbelief.getUnitBelief(unit, df.value_type['CRAFTSMANSHIP']) local craftsmanship = setbelief.getUnitBelief(unit, df.value_type['CRAFTSMANSHIP'])
local FAMILY = setbelief.getUnitBelief(unit, df.value_type['FAMILY']) local family = setbelief.getUnitBelief(unit, df.value_type['FAMILY'])
local HARMONY = setbelief.getUnitBelief(unit, df.value_type['HARMONY']) local harmony = setbelief.getUnitBelief(unit, df.value_type['HARMONY'])
local INDEPENDENCE = setbelief.getUnitBelief(unit, df.value_type['INDEPENDENCE']) local independence = setbelief.getUnitBelief(unit, df.value_type['INDEPENDENCE'])
local KNOWLEDGE = setbelief.getUnitBelief(unit, df.value_type['KNOWLEDGE']) local knowledge = setbelief.getUnitBelief(unit, df.value_type['KNOWLEDGE'])
local LEISURE_TIME = setbelief.getUnitBelief(unit, df.value_type['LEISURE_TIME']) local leisure_time = setbelief.getUnitBelief(unit, df.value_type['LEISURE_TIME'])
local NATURE = setbelief.getUnitBelief(unit, df.value_type['NATURE']) local nature = setbelief.getUnitBelief(unit, df.value_type['NATURE'])
local SKILL = setbelief.getUnitBelief(unit, df.value_type['SKILL']) local skill = setbelief.getUnitBelief(unit, df.value_type['SKILL'])
-- Calculate the rating using the defined variables -- calculate the rating using the defined variables
local rating = (CRAFTSMANSHIP * -0.01) + (FAMILY * -0.09) + (HARMONY * 0.05) local rating = (craftsmanship * -0.01) + (family * -0.09) + (harmony * 0.05)
+ (INDEPENDENCE * 0.06) + (KNOWLEDGE * -0.30) + (LEISURE_TIME * 0.24) + (independence * 0.06) + (knowledge * -0.30) + (leisure_time * 0.24)
+ (NATURE * 0.27) + (SKILL * -0.21) + (ALTRUISM * 0.13) + (nature * 0.27) + (skill * -0.21) + (altruism * 0.13)
+ (ANXIETY_PROPENSITY * -0.06) + (BRAVERY * 0.06) + (anxiety_propensity * -0.06) + (bravery * 0.06)
+ (CHEER_PROPENSITY * 0.41) + (CURIOUS * -0.06) + (DISCORD * 0.14) + (cheer_propensity * 0.41) + (curious * -0.06) + (discord * 0.14)
+ (DUTIFULNESS * -0.03) + (EMOTIONALLY_OBSESSIVE * -0.13) + (dutifulness * -0.03) + (emotionally_obsessive * -0.13)
+ (HUMOR * -0.05) + (LOVE_PROPENSITY * 0.15) + (PERSEVERENCE * -0.07) + (humor * -0.05) + (love_propensity * 0.15) + (perseverence * -0.07)
+ (POLITENESS * -0.14) + (PRIVACY * 0.03) + (STRESS_VULNERABILITY * -0.20) + (politeness * -0.14) + (privacy * 0.03) + (stress_vulnerability * -0.20)
+ (TOLERANT * -0.11) + (tolerant * -0.11)
return rating return rating
end end
@ -1148,9 +1148,7 @@ function SquadAssignmentOverlay:refresh_list(sort_widget, sort_fn)
end end
function SquadAssignmentOverlay:onInput(keys) function SquadAssignmentOverlay:onInput(keys)
if keys._MOUSE_R_DOWN or if keys._MOUSE_R or (keys._MOUSE_L and not self:getMouseFramePos()) then
keys._MOUSE_L_DOWN and not self:getMouseFramePos()
then
-- if any click is made outside of our window, we may need to refresh our list -- if any click is made outside of our window, we may need to refresh our list
self.dirty = true self.dirty = true
end end

@ -801,10 +801,12 @@ end
function AssignAnimalScreen:onInput(keys) function AssignAnimalScreen:onInput(keys)
local handled = AssignAnimalScreen.super.onInput(self, keys) local handled = AssignAnimalScreen.super.onInput(self, keys)
if not self.is_valid_ui_state() then if not self.is_valid_ui_state() then
if view then
view:dismiss() view:dismiss()
end
return return
end end
if keys._MOUSE_L_DOWN then if keys._MOUSE_L then
-- if any click is made outside of our window, we need to recheck unit properties -- if any click is made outside of our window, we need to recheck unit properties
local window = self.subviews[1] local window = self.subviews[1]
if not window:getMouseFramePos() then if not window:getMouseFramePos() then
@ -818,7 +820,7 @@ function AssignAnimalScreen:onInput(keys)
end end
function AssignAnimalScreen:onRenderFrame() function AssignAnimalScreen:onRenderFrame()
if not self.is_valid_ui_state() then if view and not self.is_valid_ui_state() then
view:dismiss() view:dismiss()
end end
end end
@ -1072,6 +1074,7 @@ function CageChainOverlay:init()
frame={t=0, l=0, r=0, h=1}, frame={t=0, l=0, r=0, h=1},
label='DFHack assign', label='DFHack assign',
key='CUSTOM_CTRL_T', key='CUSTOM_CTRL_T',
visible=is_valid_building,
on_activate=function() view = view and view:raise() or show_cage_chain_screen() end, on_activate=function() view = view and view:raise() or show_cage_chain_screen() end,
}, },
} }

@ -10,6 +10,7 @@
#include "json/json.h" #include "json/json.h"
#include "df/building.h" #include "df/building.h"
#include "df/gamest.h"
#include "df/historical_figure.h" #include "df/historical_figure.h"
#include "df/itemdef_ammost.h" #include "df/itemdef_ammost.h"
#include "df/itemdef_armorst.h" #include "df/itemdef_armorst.h"
@ -36,6 +37,8 @@
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
using df::global::game;
DFHACK_PLUGIN("orders"); DFHACK_PLUGIN("orders");
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
@ -64,6 +67,8 @@ static command_result orders_export_command(color_ostream & out, const std::stri
static command_result orders_import_command(color_ostream & out, const std::string & name); static command_result orders_import_command(color_ostream & out, const std::string & name);
static command_result orders_clear_command(color_ostream & out); static command_result orders_clear_command(color_ostream & out);
static command_result orders_sort_command(color_ostream & out); static command_result orders_sort_command(color_ostream & out);
static command_result orders_recheck_command(color_ostream & out);
static command_result orders_recheck_current_command(color_ostream & out);
static command_result orders_command(color_ostream & out, std::vector<std::string> & parameters) static command_result orders_command(color_ostream & out, std::vector<std::string> & parameters)
{ {
@ -111,6 +116,19 @@ static command_result orders_command(color_ostream & out, std::vector<std::strin
return orders_sort_command(out); return orders_sort_command(out);
} }
if (parameters[0] == "recheck" && parameters.size() == 1)
{
return orders_recheck_command(out);
}
if (parameters[0] == "recheck" && parameters.size() == 2)
{
if (parameters[1] == "this")
{
return orders_recheck_current_command(out);
}
}
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
@ -1015,3 +1033,27 @@ static command_result orders_sort_command(color_ostream & out)
return CR_OK; return CR_OK;
} }
static command_result orders_recheck_command(color_ostream & out)
{
for (auto it : world->manager_orders)
{
it->status.bits.active = false;
it->status.bits.validated = false;
}
return CR_OK;
}
static command_result orders_recheck_current_command(color_ostream & out)
{
if (game->main_interface.info.work_orders.conditions.open)
{
game->main_interface.info.work_orders.conditions.wq->status.bits.active = false;
}
else
{
out << COLOR_LIGHTRED << "Order conditions is not open" << std::endl;
return CR_FAILURE;
}
return CR_OK;
}

@ -86,7 +86,7 @@ struct viewscreen_overlay : T {
if (!input_is_handled) if (!input_is_handled)
INTERPOSE_NEXT(feed)(input); INTERPOSE_NEXT(feed)(input);
else else
enabler->last_text_input[0] = '\0'; dfhack_lua_viewscreen::markInputAsHandled();
} }
DEFINE_VMETHOD_INTERPOSE(void, render, ()) { DEFINE_VMETHOD_INTERPOSE(void, render, ()) {
INTERPOSE_NEXT(render)(); INTERPOSE_NEXT(render)();

@ -36,7 +36,7 @@ namespace DFHack {
static std::vector<TexposHandle> textures; static std::vector<TexposHandle> textures;
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) { DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) {
textures = Textures::loadTileset("hack/data/art/pathable.png", 32, 32); textures = Textures::loadTileset("hack/data/art/pathable.png", 32, 32, true);
return CR_OK; return CR_OK;
} }

@ -1 +1 @@
Subproject commit e0591830b72cdfaec5c9bdb1bf713a74fe744788 Subproject commit d2ad86165e89dc3b0f262eea00db8e2347cc4421

@ -42,17 +42,17 @@ function test.editfield_click()
expect.eq(5, e.cursor) expect.eq(5, e.cursor)
mock.patch(e, 'getMousePos', mock.func(0), function() mock.patch(e, 'getMousePos', mock.func(0), function()
e:onInput{_MOUSE_L=true} e:onInput{_MOUSE_L_DOWN=true}
expect.eq(1, e.cursor) expect.eq(1, e.cursor)
end) end)
mock.patch(e, 'getMousePos', mock.func(20), function() mock.patch(e, 'getMousePos', mock.func(20), function()
e:onInput{_MOUSE_L=true} e:onInput{_MOUSE_L_DOWN=true}
expect.eq(5, e.cursor, 'should only seek to end of text') expect.eq(5, e.cursor, 'should only seek to end of text')
end) end)
mock.patch(e, 'getMousePos', mock.func(2), function() mock.patch(e, 'getMousePos', mock.func(2), function()
e:onInput{_MOUSE_L=true} e:onInput{_MOUSE_L_DOWN=true}
expect.eq(3, e.cursor) expect.eq(3, e.cursor)
end) end)
end end

@ -59,37 +59,37 @@ function test.onInput()
s:update(23, 10, 50) s:update(23, 10, 50)
expect.false_(s:onInput{}, 'no mouse down') expect.false_(s:onInput{}, 'no mouse down')
expect.false_(s:onInput{_MOUSE_L_DOWN=true}, 'no y coord') expect.false_(s:onInput{_MOUSE_L=true}, 'no y coord')
spec, y = nil, 0 spec, y = nil, 0
expect.true_(s:onInput{_MOUSE_L_DOWN=true}) expect.true_(s:onInput{_MOUSE_L=true})
expect.eq('up_small', spec, 'on up arrow') expect.eq('up_small', spec, 'on up arrow')
spec, y = nil, 1 spec, y = nil, 1
expect.true_(s:onInput{_MOUSE_L_DOWN=true}) expect.true_(s:onInput{_MOUSE_L=true})
expect.eq('up_large', spec, 'on body above bar') expect.eq('up_large', spec, 'on body above bar')
spec, y = nil, 44 spec, y = nil, 44
expect.true_(s:onInput{_MOUSE_L_DOWN=true}) expect.true_(s:onInput{_MOUSE_L=true})
expect.eq('up_large', spec, 'on body just above bar') expect.eq('up_large', spec, 'on body just above bar')
spec, y = nil, 45 spec, y = nil, 45
expect.true_(s:onInput{_MOUSE_L_DOWN=true}) expect.true_(s:onInput{_MOUSE_L=true})
expect.nil_(spec, 'on top of bar') expect.nil_(spec, 'on top of bar')
spec, y = nil, 63 spec, y = nil, 63
expect.true_(s:onInput{_MOUSE_L_DOWN=true}) expect.true_(s:onInput{_MOUSE_L=true})
expect.nil_(spec, 'on bottom of bar') expect.nil_(spec, 'on bottom of bar')
spec, y = nil, 64 spec, y = nil, 64
expect.true_(s:onInput{_MOUSE_L_DOWN=true}) expect.true_(s:onInput{_MOUSE_L=true})
expect.eq('down_large', spec, 'on body just below bar') expect.eq('down_large', spec, 'on body just below bar')
spec, y = nil, 98 spec, y = nil, 98
expect.true_(s:onInput{_MOUSE_L_DOWN=true}) expect.true_(s:onInput{_MOUSE_L=true})
expect.eq('down_large', spec, 'on body below bar') expect.eq('down_large', spec, 'on body below bar')
spec, y = nil, 99 spec, y = nil, 99
expect.true_(s:onInput{_MOUSE_L_DOWN=true}) expect.true_(s:onInput{_MOUSE_L=true})
expect.eq('down_small', spec, 'on down arrow') expect.eq('down_small', spec, 'on down arrow')
end end

@ -7,7 +7,7 @@ function test.hotkeylabel_click()
local l = widgets.HotkeyLabel{key='SELECT', on_activate=func} local l = widgets.HotkeyLabel{key='SELECT', on_activate=func}
mock.patch(l, 'getMousePos', mock.func(0), function() mock.patch(l, 'getMousePos', mock.func(0), function()
l:onInput{_MOUSE_L_DOWN=true} l:onInput{_MOUSE_L=true}
expect.eq(1, func.call_count) expect.eq(1, func.call_count)
end) end)
end end
@ -33,7 +33,7 @@ function test.togglehotkeylabel_click()
local l = widgets.ToggleHotkeyLabel{} local l = widgets.ToggleHotkeyLabel{}
expect.true_(l:getOptionValue()) expect.true_(l:getOptionValue())
mock.patch(l, 'getMousePos', mock.func(0), function() mock.patch(l, 'getMousePos', mock.func(0), function()
l:onInput{_MOUSE_L_DOWN=true} l:onInput{_MOUSE_L=true}
expect.false_(l:getOptionValue()) expect.false_(l:getOptionValue())
end) end)
end end

@ -1,5 +1,5 @@
config.mode = 'fortress' config.mode = 'fortress'
--config.target = 'orders' config.target = 'orders'
local FILE_PATH_PATTERN = 'dfhack-config/orders/%s.json' local FILE_PATH_PATTERN = 'dfhack-config/orders/%s.json'