diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 3bc931305a..c2d946e300 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -66,7 +66,7 @@ on: default: false gcc-ver: type: string - default: "10" + default: "11" jobs: build-linux64: diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 9257a21081..1dec211117 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -66,12 +66,20 @@ on: jobs: build-win64: name: Build win64 - runs-on: ubuntu-22.04 + runs-on: windows-2022 steps: - - name: Install dependencies + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.x' + - name: Install build dependencies + run: | + choco install sccache + pip install Jinja2 + - name: Install doc dependencies + if: inputs.docs run: | - sudo apt-get update - sudo apt-get install ccache + pip install sphinx - name: Clone DFHack uses: actions/checkout@v4 with: @@ -108,49 +116,82 @@ jobs: ref: main ssh-key: ${{ secrets.DFHACK_3RDPARTY_TOKEN }} path: depends/steam + - name: Prepare output directories + run: | + mkdir output + mkdir pdb + - name: Get sccache path + run: echo ("SCCACHE_DIR=" + $env:LOCALAPPDATA + "\Mozilla\sccache\cache") >> $env:GITHUB_ENV - name: Fetch ccache if: inputs.platform-files uses: actions/cache/restore@v4 with: - path: build/win64-cross/ccache + path: ${{ env.SCCACHE_DIR }} key: win-msvc-${{ inputs.cache-id }}-${{ github.sha }} restore-keys: | win-msvc-${{ inputs.cache-id }} win-msvc - - name: Cross-compile + - uses: ilammy/msvc-dev-cmd@v1 + - name: Configure DFHack + run: | + cmake ` + -S . ` + -B build ` + -GNinja ` + -DDFHACK_BUILD_ARCH=64 ` + -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_INSTALL_PREFIX=output ` + -DCMAKE_C_COMPILER_LAUNCHER=sccache ` + -DCMAKE_CXX_COMPILER_LAUNCHER=sccache ` + -DBUILD_PDBS:BOOL=${{ inputs.cache-id == 'release' }} ` + -DDFHACK_RUN_URL='https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' ` + -DBUILD_LIBRARY=${{ inputs.platform-files }} ` + -DBUILD_PLUGINS:BOOL=${{ inputs.platform-files && inputs.plugins }} ` + -DBUILD_STONESENSE:BOOL=${{ inputs.stonesense }} ` + -DBUILD_DOCS:BOOL=${{ inputs.docs }} ` + -DBUILD_DOCS_NO_HTML:BOOL=${{ !inputs.html }} ` + -DINSTALL_DATA_FILES:BOOL=${{ inputs.common-files }} ` + -DBUILD_DFLAUNCH:BOOL=${{ inputs.launchdf }} ` + -DBUILD_TESTS:BOOL=${{ inputs.tests }} ` + -DBUILD_XMLDUMP:BOOL=${{ inputs.xml-dump-type-sizes }} ` + ${{ inputs.xml-dump-type-sizes && '-DINSTALL_XMLDUMP:BOOL=1' || '' }} + - name: Build DFHack env: - CMAKE_EXTRA_ARGS: -DBUILD_PDBS:BOOL=${{ inputs.cache-id == 'release' }} -DDFHACK_RUN_URL='https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' -DBUILD_LIBRARY=${{ inputs.platform-files }} -DBUILD_PLUGINS:BOOL=${{ inputs.platform-files && inputs.plugins }} -DBUILD_STONESENSE:BOOL=${{ inputs.stonesense }} -DBUILD_DOCS:BOOL=${{ inputs.docs }} -DBUILD_DOCS_NO_HTML:BOOL=${{ !inputs.html }} -DINSTALL_DATA_FILES:BOOL=${{ inputs.common-files }} -DINSTALL_SCRIPTS:BOOL=${{ inputs.common-files }} -DBUILD_DFLAUNCH:BOOL=${{ inputs.launchdf }} -DBUILD_TESTS:BOOL=${{ inputs.tests }} -DBUILD_XMLDUMP:BOOL=${{ inputs.xml-dump-type-sizes }} ${{ inputs.xml-dump-type-sizes && '-DINSTALL_XMLDUMP:BOOL=1' || '' }} + SCCACHE_CACHE_SIZE: 500M run: | - cd build - bash -x build-win64-from-linux.sh + ninja install -C build - name: Finalize cache run: | cd build - ccache -d win64-cross/ccache --show-stats --verbose - ccache -d win64-cross/ccache --max-size ${{ inputs.cache-id == 'release' && '500M' || '150M' }} - ccache -d win64-cross/ccache --cleanup - ccache -d win64-cross/ccache --max-size ${{ inputs.cache-id == 'release' && '2G' || '500M' }} - ccache -d win64-cross/ccache --zero-stats + sccache --show-stats + sccache --zero-stats - name: Save ccache if: inputs.platform-files && !inputs.cache-readonly uses: actions/cache/save@v4 with: - path: build/win64-cross/ccache + path: ${{ env.SCCACHE_DIR }} key: win-msvc-${{ inputs.cache-id }}-${{ github.sha }} - name: Format artifact name if: inputs.artifact-name id: artifactname run: | - if test "false" = "${{ inputs.append-date-and-hash }}"; then - echo name=${{ inputs.artifact-name }} >> $GITHUB_OUTPUT - else - echo name=${{ inputs.artifact-name }}-$(date +%Y%m%d)-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT - fi + if ("${{ inputs.append-date-and-hash }}" -eq "false") { + "name=${{ inputs.artifact-name }}" | Out-File -Append $env:GITHUB_OUTPUT + } else { + $date = Get-Date -Format "yyyMMdd" + $hash = git rev-parse --short HEAD + "name=${{ inputs.artifact-name}}-$date-$hash" | Out-File -Append $env:GITHUB_OUTPUT + } + - name: Prep pdbs + if: inputs.artifact-name && inputs.cache-id == 'release' + run: | + Get-ChildItem -Recurse -File -Path "build" -Filter *.pdb | + Copy-Item -Destination "pdb" - name: Prep artifact - if: inputs.artifact-name run: | - cd build/win64-cross/output - tar cjf ../../../${{ steps.artifactname.outputs.name }}.tar.bz2 . + cd output + 7z a -ttar -so -an . | + 7z a -si -tbzip2 ../${{ steps.artifactname.outputs.name }}.tar.bz2 - name: Upload artifact if: inputs.artifact-name uses: actions/upload-artifact@v4 @@ -162,4 +203,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: ${{ steps.artifactname.outputs.name }}_pdb - path: build/win64-cross/pdb + path: pdb diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a73bb3f82d..175a1adf48 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,9 +59,9 @@ jobs: fail-fast: false matrix: include: - - gcc: 10 + - gcc: 11 # baseline compatibility with ubuntu LTS 22.04 plugins: "default" - - gcc: 12 + - gcc: 12 # highest available in ubuntu 22.04 plugins: "all" test-windows: @@ -96,7 +96,7 @@ jobs: dfhack_repo: ${{ inputs.dfhack_repo }} dfhack_ref: ${{ inputs.dfhack_ref }} os: ubuntu - compiler: gcc-10 + compiler: gcc-11 plugins: default config: default diff --git a/.github/workflows/watch-df-release.yml b/.github/workflows/watch-df-release.yml index 77f4947781..50950140d1 100644 --- a/.github/workflows/watch-df-release.yml +++ b/.github/workflows/watch-df-release.yml @@ -21,11 +21,11 @@ jobs: # steam_branch: leave blank if no DFHack steam push is desired include: - df_steam_branch: public - - df_steam_branch: beta - - df_steam_branch: experimental - structures_ref: experimental - dfhack_ref: experimental - steam_branch: experimental +# - df_steam_branch: beta +# - df_steam_branch: experimental +# structures_ref: experimental +# dfhack_ref: experimental +# steam_branch: experimental steps: - name: Fetch state uses: actions/cache/restore@v4 diff --git a/.gitignore b/.gitignore index 60a0e1b6b2..eda2b226d5 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ build/compile_commands.json build/dfhack_setarch.txt build/ImportExecutables.cmake build/Testing +build/_deps # Python binding binaries *.pyc @@ -64,6 +65,7 @@ build/CPack*Config.cmake # VSCode files .vscode +*.code-workspace # ctags file tags diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d79077b318..47251d8ed3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: repos: # shared across repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -20,11 +20,11 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.33.2 + rev: 0.37.4 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.5 + rev: v1.5.6 hooks: - id: forbid-tabs exclude_types: diff --git a/CMakeLists.txt b/CMakeLists.txt index cab5c6a8ce..61bddcc0b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,11 @@ cmake_minimum_required(VERSION 3.18 FATAL_ERROR) cmake_policy(SET CMP0048 NEW) cmake_policy(SET CMP0074 NEW) +set(CMAKE_INSTALL_MESSAGE "LAZY") # set up versioning. -set(DF_VERSION "52.04") -set(DFHACK_RELEASE "r1") +set(DF_VERSION "53.15") +set(DFHACK_RELEASE "r2") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") @@ -46,8 +47,8 @@ option(REMOVE_SYMBOLS_FROM_DF_STUBS "Remove debug symbols from DF stubs. (Reduce macro(CHECK_GCC compiler_path) execute_process(COMMAND ${compiler_path} -dumpversion OUTPUT_VARIABLE GCC_VERSION_OUT) string(STRIP "${GCC_VERSION_OUT}" GCC_VERSION_OUT) - if(${GCC_VERSION_OUT} VERSION_LESS "10") - message(SEND_ERROR "${compiler_path} version ${GCC_VERSION_OUT} cannot be used - use GCC 10 or later") + if(${GCC_VERSION_OUT} VERSION_LESS "11") + message(SEND_ERROR "${compiler_path} version ${GCC_VERSION_OUT} cannot be used - use GCC 11 or later") endif() endmacro() @@ -122,6 +123,9 @@ if(MSVC) # Enable C5038 - This is equivalent to gcc's -Werror=reorder, which is enabled by default by gcc -Wall add_compile_options("/w15038") + # Enable C4062 - Warns about missing enum case in switch statement, equivalent to gcc -Wswitch + add_compile_options("/w14062") + # MSVC panics if an object file contains more than 65,279 sections. this # happens quite frequently with code that uses templates, such as vectors. add_compile_options("/bigobj") @@ -223,13 +227,10 @@ set(DFHACK_DATA_DESTINATION hack) ## where to install things (after the build is done, classic 'make install' or package structure) # the dfhack libraries will be installed here: -if(UNIX) - # put the lib into DF/hack - set(DFHACK_LIBRARY_DESTINATION ${DFHACK_DATA_DESTINATION}) -else() - # windows is crap, therefore we can't do nice things with it. leave the libs on a nasty pile... - set(DFHACK_LIBRARY_DESTINATION .) -endif() + +# put the lib into DF/hack +# windows will find it because dfhooks will `AddDllDirectory` the hack folder at runtime +set(DFHACK_LIBRARY_DESTINATION ${DFHACK_DATA_DESTINATION}) # external tools will be installed here: set(DFHACK_BINARY_DESTINATION .) @@ -264,7 +265,7 @@ if(UNIX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32 -march=i686") endif() string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - set(CMAKE_INSTALL_RPATH ${DFHACK_LIBRARY_DESTINATION}) + set(CMAKE_INSTALL_RPATH "$ORIGIN") elseif(MSVC) # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP") @@ -344,6 +345,17 @@ if(BUILD_LIBRARY) endif() endif() +# this can be made conditional once we get to better platform support for std::format +INCLUDE(FetchContent) +FetchContent_Declare( + fmt + GIT_REPOSITORY https://github.com/fmtlib/fmt.git + GIT_TAG 790b9389ae99c4ddebdd2736a8602eca1fec684e # 12.1.0 + bugfix for MSVC warning + build time improvements +) +FetchContent_MakeAvailable(fmt) +set(FMTLIB fmt) +add_definitions("-DUSE_FMTLIB") + if(APPLE) # libstdc++ (GCC 4.8.5 for OS X 10.6) # fixes crash-on-unwind bug in DF's libstdc++ @@ -425,7 +437,7 @@ macro(dfhack_test name files) if(BUILD_LIBRARY AND UNIX AND NOT APPLE) # remove this once our MSVC build env has been updated add_executable(${name} ${files}) target_include_directories(${name} PUBLIC depends/googletest/googletest/include) - target_link_libraries(${name} dfhack gtest) + target_link_libraries(${name} dfhack ${FMTLIB} gtest) add_test(NAME ${name} COMMAND ${name}) endif() endmacro() @@ -523,7 +535,7 @@ if(BUILD_DOCS) add_custom_command(OUTPUT ${SPHINX_OUTPUT} COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/docs/build.py" - ${SPHINX_BUILD_TARGETS} --sphinx="${SPHINX_EXECUTABLE}" -- -q -W + ${SPHINX_BUILD_TARGETS} --sphinx="${SPHINX_EXECUTABLE}" --quiet -- -W DEPENDS ${SPHINX_DEPS} COMMENT "Building documentation with Sphinx" ) diff --git a/build/.gitignore b/build/.gitignore index 11f87d8c3e..0675a165cf 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -8,3 +8,4 @@ _CPack_Packages .cmake win64-cross dest +DF diff --git a/depends/dfhooks b/depends/dfhooks index 481dc1a12b..5a904e30a8 160000 --- a/depends/dfhooks +++ b/depends/dfhooks @@ -1 +1 @@ -Subproject commit 481dc1a12b1264ef06ce95e331ef35cbfa0e6ace +Subproject commit 5a904e30a8bace81c662b44ec7ff076b92edafd1 diff --git a/docs/about/Authors.rst b/docs/about/Authors.rst index 8743b75a19..a154d314b6 100644 --- a/docs/about/Authors.rst +++ b/docs/about/Authors.rst @@ -163,6 +163,7 @@ Omniclasm Ong Ying Gao ong-yinggao98 oorzkws oorzkws OwnageIsMagic OwnageIsMagic +pajawojciech pajawojciech palenerd dlmarquis PassionateAngler PassionateAngler Patrik Lundell PatrikLundell diff --git a/docs/about/Removed.rst b/docs/about/Removed.rst index 5f73b86be3..bf3d0118df 100644 --- a/docs/about/Removed.rst +++ b/docs/about/Removed.rst @@ -258,6 +258,13 @@ gui/hack-wish ============= Replaced by `gui/create-item`. +.. _gui/logcleaner: + +gui/logcleaner +============== +Removed because changes to Dwarf Fortress internals made the functionality +impossible to implement safely. + .. _gui/manager-quantity: gui/manager-quantity @@ -278,6 +285,13 @@ Tool that warned the user when the ``dfhack.init`` file did not exist. Now that ``dfhack.init`` is autogenerated in ``dfhack-config/init``, this warning is no longer necessary. +.. _logcleaner: + +logcleaner +=============== +Removed because changes to Dwarf Fortress internals made the functionality +impossible to implement safely. + .. _masspit: masspit diff --git a/docs/build.py b/docs/build.py index bfb6780b23..bf0dd9e488 100755 --- a/docs/build.py +++ b/docs/build.py @@ -62,6 +62,8 @@ def output_format(s): help='Sphinx executable to run [environment variable: SPHINX; default: "sphinx-build"]') parser.add_argument('-j', '--jobs', type=str, default=os.environ.get('JOBS', 'auto'), help='Number of Sphinx threads to run [environment variable: JOBS; default: "auto"]') + parser.add_argument('-q', '--quiet', action='store_true', + help='Disable most output on stdout (also passed to sphinx-build)') parser.add_argument('--debug', action='store_true', help='Log commands that are run, etc.') parser.add_argument('--offline', action='store_true', @@ -91,6 +93,8 @@ def output_format(s): command = [args.sphinx] + OUTPUT_FORMATS[format_name].args + ['-j', args.jobs] if args.clean: command += ['-E'] + if args.quiet: + command += ['-q'] command += forward_args if args.debug: @@ -98,4 +102,5 @@ def output_format(s): print('Running:', command) subprocess.run(command, check=True, env=sphinx_env) - print('') + if not args.quiet: + print('') diff --git a/docs/builtins/keybinding.rst b/docs/builtins/keybinding.rst index 3e6970b6fc..e8f206848d 100644 --- a/docs/builtins/keybinding.rst +++ b/docs/builtins/keybinding.rst @@ -9,9 +9,9 @@ Like any other command, it can be used at any time from the console, but bindings are not remembered between runs of the game unless re-created in :file:`dfhack-config/init/dfhack.init`. -Hotkeys can be any combinations of Ctrl/Alt/Shift with A-Z, 0-9, F1-F12, or ` -(the key below the :kbd:`Esc` key on most keyboards). You can also represent -mouse buttons beyond the first three with ``MOUSE4`` through ``MOUSE15``. +Hotkeys can be any combinations of Ctrl/Alt/Super/Shift with any key recognized by SDL. +You can also represent mouse buttons beyond the first three with ``MOUSE4`` +through ``MOUSE15``. Usage ----- @@ -27,12 +27,13 @@ Usage ``keybinding set "" ["" ...]`` Clear, and then add bindings for the specified key. -The ```` parameter above has the following **case-sensitive** syntax:: +The ```` parameter above has the following case-insensitive syntax:: - [Ctrl-][Alt-][Shift-]KEY[@context[|context...]] + [Ctrl-][Alt-][Super-][Shift-]KEY[@context[|context...]] where the ``KEY`` part can be any recognized key and :kbd:`[`:kbd:`]` denote -optional parts. +optional parts. It is important to note that the key is the non-shifted version +of the key. For example ``!`` would be defined as ``Shift-0``. DFHack commands can advertise the contexts in which they can be usefully run. For example, a command that acts on a selected unit can tell `keybinding` that diff --git a/docs/changelog.txt b/docs/changelog.txt index f6421c7d89..af1e77905d 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -1,26 +1,30 @@ === Scroll down for changes ===[[[ -The text below is included in docs/dev/Documentation.rst - see that file for more details on the changelog setup. -This is kept in this file as a quick syntax reference. +For information on how to edit/build the changelogs, see `docs/html/docs/dev/Documentation.html` or `docs/dev/Documentation.rst`. -===help +The text between the `syntax-reference` markers is included in `docs/dev/Documentation.rst`, so it must be valid RST. +It is kept in this file as a quick syntax reference. -changelog.txt uses a syntax similar to RST, with a few special sequences: +===syntax-reference-start + +The changelogs use a syntax similar to RST, with a few special sequences: - ``===`` indicates the start of a comment - ``#`` indicates the start of a release name (do not include "DFHack") -- ``##`` indicates the start of a section name (this must be listed in ``gen_changelog.py``) +- ``##`` indicates the start of a section name (which must be listed in ``docs/sphinx_extensions/dfhack/changelog.py``) - ``-`` indicates the start of a changelog entry. **Note:** an entry currently must be only one line. -- ``:`` (colon followed by space) separates the name of a feature from a description of a change to that feature. - Changes made to the same feature are grouped if they end up in the same section. -- ``:\`` (colon, backslash, space) avoids the above behavior -- ``- @`` (the space is optional) indicates the start of an entry that should only be displayed in NEWS-dev.rst. - Use this sparingly, e.g. for immediate fixes to one development build in another development build that - are not of interest to users of stable builds only. +- ``:`` (followed by space) separates the name of a feature from a description of a change to that feature. + - Changes made to the same feature are grouped if they end up in the same section. +- ``:\`` (followed by space) avoids the above behavior +- ``- @`` (the space is optional) indicates the start of an entry that should only be displayed in ``NEWS-dev.rst``. + - Use this sparingly, e.g. for immediate fixes to one development build in another development build that + are not of interest to users of stable builds only. - Three ``[`` characters indicate the start of a block (possibly a comment) that spans multiple lines. Three ``]`` characters indicate the end of such a block. -- ``!`` immediately before a phrase set up to be replaced (see gen_changelog.py) stops that occurrence from being replaced. +- ``!`` immediately before a configured replacement (see ``docs/sphinx_extensions/dfhack/changelog.py``) stops that occurrence from being replaced. + +===syntax-reference-end Template for new versions: @@ -40,7 +44,6 @@ Template for new versions: ## Removed -===end ]]] ================================================================================ @@ -56,6 +59,546 @@ Template for new versions: ## New Features ## Fixes +- `buildingplan`: restore planner UI elements: hollow constructions, only engraved slabs, only empty cages, weapon count + +## Misc Improvements + +## Documentation + +## API + +- ``Screen``: new functions ``paintMapPortTile`` and ``readMapPortTile`` to write and read world and region map tiles. + +## Lua + +- Added ``Screen::paintMapPortTile`` as ``dfhack.screen.paintMapPortTile`` +- Added ``Screen::readMapPortTile`` as ``dfhack.screen.readMapPortTile`` + + +## Removed + +# 53.15-r2 + +## New Tools + +## New Features +- `buildingplan`: add a ``Pull`` button next to linked levers on a building's "Show linked buildings" tab so you can queue a high-priority pull-lever job (or cancel a queued one) without navigating to the lever + +## Fixes +- `autoclothing`: correct defect in validating material specification on command line +- `autolabor`: Fix running 1 tick less frequently than intended. +- `buildingplan`: fixed non-clickable pressure plates's triggers (issue #5736) +- `getplants`: added protective code to avoid misoperation when a plant has an invalid material (which should never happen, but...) + +## Misc Improvements +- `buildingplan`: added a slider on the weapontrap overlay +- `buildingplan`: added a small tooltip text about renaming favorites in the UI +- `buildingplan`: buildingplan can now generate work orders +- `orders`: exported orders now include a human-readable ``name`` field + +## Documentation + +## API +- Core: added ``getConfigPath()`` API for obtaining the path to user-specific configuration files +- ``widgets.RadioButton``: New button widget resembling those used in ``gui/control-panel`` + +## Lua +- Added ``dfhack.getConfigPath()`` API, proxying ``Core::getConfigPath`` + +## Removed + +# 53.15-r1 + +## New Tools + +## New Features + +## Fixes +- `autoclothing`: will no longer count gloves and pants as if they were helms +- `timestream`: do not skip ticks when a caravan is loading or unloading, and be more careful about skipping ticks when flows are active + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.14-r2 + +## New Tools + +## New Features + +## Fixes + +## Misc Improvements +- Core: attempts to delete a pool-allocated DF object will now throw an exception instead of corrupting the heap + +## Documentation + +## API + +## Lua + +## Removed +- `logcleaner`: Removed (cannot be safely implemented at this time) + +# 53.14-r1 + +## New Tools + +## New Features +- Compatibility with Dwarf Fortress 53.14 + +## Fixes + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.13-r2 + +## New Tools + +## New Features + +## Fixes +- ``Gui::makeAnnoucement``, ``Gui::showPopupAnnouncement`, and ``Gui::autoDFAnnouncement`` will no longer attempt to cull the DF announcement vector + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.13-r1 + +## New Tools + +## New Features + +## Fixes + +## Misc Improvements + +## Documentation +- updated documentation for ``autofarm`` for more clarity + +## API +- add flexible casting to ``enum_field`` to enable explicit casting to more types +- Handle units without current soul in ``Units::getFocusPenalty`` + +## Lua + +## Removed + +# 53.12-r1 + +## New Tools + +## New Features +- Compatibility with Dwarf Fortress 53.12 + +## Fixes +- Stockpile definitions in the default library will be correctly found and used (fixed missing path separator) + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.11-r3 + +## New Tools + +## New Features + +## Fixes +- Core: Windows console will always use UTF-8 regardless of system code page settings +- Steam launcher: Switch to injection strategy, allowing Dwarf Fortress and DFHack to be installed in disparate locations + +## Misc Improvements +- Make DFHack relocatable so that it doesn't depend on being fully co-installed with Dwarf Fortress + +## Documentation + +## API + +## Lua + +## Removed + +# 53.11-r2 + +## New Tools + +## New Features + +## Fixes +- `autoclothing`, `autoslab`, `tailor`: orders will no longer be created with a repetition frequency of ``NONE`` + +## Misc Improvements +- General: DFHack will unconditionally use UTF-8 for the console on Windows, now that DF forces the process effective system code page to 65001 during startup + +## Documentation + +## API + +## Lua + +## Removed + +# 53.11-r1 + +## New Tools + +## New Features + +## Fixes +- `sort`: correct misspelling of ``PERSEVERENCE``; fixes "hates combat" filter in squad selection screen + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.10-r2 + +## New Tools +- ``logcleaner``: New plugin for time-triggered clearing of combat, sparring, and hunting reports with configurable filtering and overlay UI. + +## New Features +- `orders`: added search overlay to find and navigate to matching manager orders with arrow indicators +- `sort`: added ``Uniformed`` filter to squad assignment screen to filter dwarves with mining, woodcutting, or hunting labors +- `sort`: Add death cause button to dead/missing tab in the creatures screen + +## Fixes + +## Misc Improvements +- Core: DFHack now validates vtable pointers in objects read from memory and will throw an exception instead of crashing when an invalid vtable pointer is encountered. This makes it easier to identify which DF data structure contains corrupted data when this manifests in the form of a bad vtable pointer, and shifts blame for such crashes from DFHack to DF. + +## Documentation + +## API +- Added ``Items::pickGrowthPrint``: given a plant material and a growth index, returns the print variant corresponding to the current in-game time. +- Added ``Items::useStandardMaterial``: given an item type, returns true if the item is made of a specific material and false if it has a race and caste instead. +- Added ``Maps::addItemSpatter``: add a spatter of the specified item + material + growth print to the indicated tile, returning whatever amount wouldn't fit in the tile. +- Added ``Maps::addMaterialSpatter``: add a spatter of the specified material + state to the indicated tile, returning whatever amount wouldn't fit in the tile. + +## Lua +- Added ``Maps::addItemSpatter`` as ``dfhack.maps.addItemSpatter``. +- Added ``Maps::addMaterialSpatter`` as ``dfhack.maps.addMaterialSpatter``. + +## Removed + +# 53.10-r1 + +## New Tools + +## New Features + +## Fixes +- `autochop`: the report will no longer throw a C++ exception when burrows are defined. +- `suspendmanager`: Fix the overlay appearing where it should not when following a unit + +## Misc Improvements + +## Documentation + +## API +- Added ``Burrows::getName``: obtains the name of a burrow, or the same placeholder name that DF would show if the burrow is unnamed. + +## Lua +- Added ``Burrows::getName`` as ``dfhack.burrows.getName``. + +## Removed + +# 53.09-r1 + +## New Tools + +## New Features +- `tweak`: ``drawbridge-tiles``: Make it so raised bridges render with different tiles in ASCII mode to make it more obvious that they ARE raised (and to indicate their direction) + +## Fixes +- ``Filesystem::as_string`` now always uses UTF-8 encoding rather than using the system locale encoding + +## Misc Improvements + +## Documentation + +## API +- ``dfhack.job.getManagerOrderName``: New function to get the display name of a manager order + +## Lua + +## Removed + +# 53.08-r1 + +## New Tools + +## New Features +- compatibility with DF 53.08 + +## Fixes + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.07-r1 + +## New Tools +- ``edgescroll``: Introduced plugin to pan the view automatically when the mouse reaches the screen border. +- `infinite-sky`: Re-enabled with compatibility with new siege map data. + +## New Features +- `sort`: Places search widget can search "Siege engines" subtab by name, loaded status, and operator status + +## Fixes +- `sort`: Using the squad unit selector will no longer cause Dwarf Fortress to crash on exit +- `sort`: Places search widget moved to account for DF's new "Siege engines" subtab + +## Misc Improvements +- `createitem`: created items can now be placed onto/into tables, nests, bookcases, display cases, and altars +- The ``fpause`` console command can now be used to force world generation to pause (as it did prior to version 50). +- `keybinding`: keybinds may now include the super key, and are no longer limited to particular keys ranges of keys, allowing any recognized by SDL. + +## Documentation + +## API +- ``Hotkey``: New module for hotkey functionality + +## Lua +- The ``Lua interactive interpreter`` banner now documents keywords such as ``unit`` and ``item`` which reference the currently-selected object in the DF UI. +- ``dfhack.hotkey.addKeybind``: Creates new keybindings +- ``dfhack.hotkey.removeKeybind``: Removes existing keybindings +- ``dfhack.hotkey.listActiveKeybinds``: Lists all keybinds for the current context +- ``dfhack.hotkey.listAllKeybinds``: Lists all keybinds for all contexts +- ``dfhack.hotkey.requestKeybindingInput``: Requests the next keybind-compatible input is saved +- ``dfhack.hotkey.getKeybindingInput``: Reads the input saved in response to a request. + +## Removed + +# 53.06-r1 + +## New Tools + +## New Features + +## Fixes + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed +- `infiniteSky`: Temporarily disabled due to incompatibility with changes made as part of DF's siege update + +# 53.05-r1 + +## New Tools + +## New Features +- compatibility with 53.05 + +## Fixes +- `sort`: Using the squad unit selector will no longer cause Dwarf Fortress to crash on exit + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.04-r1.1 + +## New Tools + +## New Features + +## Fixes +- fixed misalignment in ``widgets::unit_list`` + +## Misc Improvements + +## Documentation + +## API + +## Lua + +## Removed + +# 53.04-r1 + +## New Tools + +## New Features + +## Fixes +- `buildingplan`: Bolt throwers will no longer be constructed using populated bins. +- `RemoteFortressReader`: updated siege engine facing enums for new diagonal directions +- `suspendmanager`: treat reinforced walls as a blocking construction and buildable platform + +## Misc Improvements +- `autolabor`: support for new dying and siege-related labors +- `blueprint`: support for reinforced walls and bolt throwers + +## Documentation + +## API + +## Lua + +## Removed + +# 53.03-r1 + +## New Tools + +## New Features + +## Fixes + +## Misc Improvements +- Release builds for Linux are now compiled with gcc 11 + +## Documentation + +## API + +## Lua + +## Removed + +# 53.02-r2 + +## New Tools + +## New Features + +## Fixes +- `buildingplan`: Building costs for reinforced walls are now correct. +- `cleanconst`: do not attempt to clean Reinforced constructions + +## Misc Improvements +- `buildingplan`: Added support for bolt throwers and siege engine rotation. + +## Documentation + +## API + +## Lua + +## Removed + +# 53.02-r1 + +## New Tools + +## New Features + +## Fixes + +## Misc Improvements +- Core: added ``gps`` (``graphicst``) to the set of globals whose sizes must agree for DFHack to pass initialization checks + +## Documentation + +## API + +## Lua + +## Removed + +# 53.01-r1 + +## New Tools + +## New Features + +## Fixes + +## Misc Improvements +- `stockpiles`: add support for managing the dyed, undyed, and color filter settings. + +## Documentation + +## API + +## Lua + +## Removed + +# 52.05-r2 + +## New Tools + +## New Features + +## Fixes +- `script-manager`: the ``scripts_modactive`` and ``scripts_modinstalled`` folders of a script-enabled mod will be properly added to the script path search list + +## Misc Improvements + +## Documentation +- added a clarification link to DF's Lua API documentation to the DFHack Lua API documentation, as a way to reduce end-user confusion + +## API + +## Lua + +## Removed + +# 52.05-r1 + +## New Tools + +## New Features + +## Fixes +- improved file system handling: gracefully handle errors from operations, preventing crashes. +- `zone`: animal assignment dialog now tolerates corrupt animal-to-pasture links. ## Misc Improvements diff --git a/docs/dev/Contributing.rst b/docs/dev/Contributing.rst index 7b34dc20ce..3ab620c1f9 100644 --- a/docs/dev/Contributing.rst +++ b/docs/dev/Contributing.rst @@ -61,6 +61,19 @@ Code format * ``#include`` directives should be sorted: C++ libraries first, then DFHack modules, then ``df/`` headers, then local includes. Within each category they should be sorted alphabetically. +General C++ code guidelines +--------------------------- +* This project is currently built at the C++20 feature level, and C++20 features should be used when appropriate. C++23 features will be allowed once all of our build platforms support them. +* NEVER use ``using namespace`` in a header file. In source files, do not use ``using namespace std``; instead, import each STL identifier you need specifically (e.g. ``using std::string;``). +* Avoid platform specific code as much as possible. +* Avoid including ``Windows.h``; if you must, ensure that ``NOMINMAX`` and ``WIN32_LEAN_AND_MEAN`` are defined before including it. +* Do not include C headers (e.g. ````); use the C++ versions (e.g. ````) instead. +* Do not use ``std::string`` (or ``char *``) for path names; always use ``std::filesystem::path``. This avoids issues with encoding, especially on the Windows platform, which is roughly 80% of our user base. +* Do not use ``printf`` or similar functions for formatting strings; use C++ streams or ``fmt::format`` instead. We use the `fmt library `__ for formatting strings; this dependency is automatically fetched by our build system. +* Avoid out parameters; prefer returning a struct, pair, or tuple, or using ``std::optional`` instead. +* Prefer range for loops to traditional for loops when iterating over a container. +* Avoid macros when possible; prefer ``constexpr`` variables for constants and functions or templates for code generation. + .. _contributing-pr-guidelines: Pull request guidelines diff --git a/docs/dev/Documentation.rst b/docs/dev/Documentation.rst index 13eaf42da0..0ba61c404d 100644 --- a/docs/dev/Documentation.rst +++ b/docs/dev/Documentation.rst @@ -67,6 +67,8 @@ with relevant tags. These are used to compile indices and generate cross-links b commands, both in the HTML documents and in-game. See the list of available `tag-list` and think about which categories your new tool belongs in. +.. _docs-links: + Links ----- @@ -418,6 +420,8 @@ Once you have pip available, you can install Sphinx with the following command:: Note that this may require opening a new (admin) command prompt if you just installed pip from the same command prompt. +.. _docs-build: + Building the documentation ========================== @@ -427,16 +431,18 @@ Sphinx to build the docs: Using CMake ----------- -See our page on `build options ` +See our page on `build options `. -Running Sphinx manually ------------------------ +Using the documentation build script +------------------------------------ You can also build the documentation without running CMake - this is faster if -you only want to rebuild the documentation regardless of any code changes. The -``docs/build.py`` script will build the documentation in any specified formats -(HTML only by default) using the same command that CMake runs when building the -docs. Run the script with ``--help`` to see additional options. +you only want to rebuild the documentation regardless of any code changes. + +The recommended approach is the ``docs/build.py`` script. This is the same +script that CMake uses internally, which wraps Sphinx with a few additional +options to handle common cases and can build multiple documentation formats with +a single invocation. Examples: @@ -449,15 +455,43 @@ Examples: * ``docs/build.py --clean`` Build HTML and force a clean build (all source files are re-read) -The resulting documentation will be stored in ``docs/html`` and/or ``docs/text``. +* ``docs/build.py --help`` + Display a full list of available options + +The resulting documentation will be stored in ``docs/html`` and/or ``docs/text`` +(or generally, a subfolder of ``docs/`` named after the requested output format(s)). + +Building a PDF version +---------------------- + +ReadTheDocs automatically builds a PDF version of the documentation (available +under the "Downloads" section when clicking on the release selector). If you +want to build a PDF version locally, you will need the ``pdflatex`` command, which is part +of a TeX distribution. The following command will then build a PDF, located in +``docs/pdf/latex/DFHack.pdf``, with default options:: + + docs/build.py pdf + +Running Sphinx manually +----------------------- + +If ``docs/build.py`` does not support what you need, you can also run Sphinx +manually. This is primarily useful for low-level debugging. -Alternatively, you can run Sphinx manually with:: +For a good starting point, add the ``--debug`` argument to your call to +``docs/build.py``. This will cause the script to print out the Sphinx command(s) +that it is running. - sphinx-build . docs/html +Some examples: -or, to build plain-text output:: +* ``sphinx-build . docs/html`` + Build the HTML docs (equivalent to ``docs/build.py``) - sphinx-build -b text . docs/text +* ``sphinx-build -b text . docs/text`` + Build the plain text docs (equivalent to ``docs/build.py text``) + +* ``sphinx-build -M latexpdf . docs/pdf`` + Build the PDF docs Sphinx has many options to enable clean builds, parallel builds, logging, and more - run ``sphinx-build --help`` for details. If you specify a different @@ -466,20 +500,47 @@ folder. Also be aware that when running ``sphinx-build`` directly, the ``docs/html`` folder may be polluted with intermediate build files that normally get written in the cmake ``build`` directory. -Building a PDF version ----------------------- +Troubleshooting +=============== -ReadTheDocs automatically builds a PDF version of the documentation (available -under the "Downloads" section when clicking on the release selector). If you -want to build a PDF version locally, you will need ``pdflatex``, which is part -of a TeX distribution. The following command will then build a PDF, located in -``docs/pdf/latex/DFHack.pdf``, with default options:: +Sphinx errors are typically printed by Sphinx, so ensure that you are not silencing Sphinx output. - docs/build.py pdf +When built with ``docs/build.py`` or CMake, errors are also logged to +``build/docs//sphinx-warnings.txt`` (for instance, if you are building +the HTML docs, ``build/docs/html/sphinx-warnings.txt``). + + +"undefined label" +----------------- + +Typical causes: + +* You have used single backticks for an inline code snippet, where double backticks should be used instead (see `docs-links`):: + + `this is an invalid inline code snippet (actually a link)` + ``this is a valid inline code snippet`` + +* You are attempting to link to a section/label, but either it does not have a label defined or you have spelled it incorrectly:: + + .. my-label: + + This is where the link should go to. + + ... + + This is `a valid link to the earlier label `. So is `my-label`. + +"toctree contains reference to document that doesn't have a title" +------------------------------------------------------------------ + +Due to the nature of our autogenerated documentation, this can sometimes occur +when switching between branches that have different autogenerated files, and can +result in autogenerated documentation (e.g. for individual tools) being missing +from the table of contents, or links failing to generate. -Alternatively, you can run Sphinx manually with:: +The quickest resolution is a clean docs build:: - sphinx-build -M latexpdf . docs/pdf + docs/build.py --clean .. _build-changelog: @@ -515,8 +576,8 @@ Changelog syntax ---------------- .. include:: /docs/changelog.txt - :start-after: ===help - :end-before: ===end + :start-after: ===syntax-reference-start + :end-before: ===syntax-reference-end .. _docs-ci: diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 86e90da9c1..f729be2743 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -25,6 +25,13 @@ It does not describe all of the utility functions implemented by Lua files located in :file:`hack/lua/*` (:file:`library/lua/*` in the git repo). +.. admonition:: Is this the DF or DFHack Lua API? + :class: warning + + This document describes the Lua API provided by DFHack, not + the Lua API provided by Dwarf Fortress. For information about DF's Lua API, see + :wiki:`Lua scripting` + on the Dwarf Fortress Wiki. .. contents:: Contents :local: @@ -931,7 +938,17 @@ can be omitted. * ``dfhack.getHackPath()`` - Returns the dfhack directory path, i.e., ``".../df/hack/"``. + Returns the DFHack installation directory path (the folder where DFHack is installed). + This may be the ``hack`` folder within the DF installation, but you should not rely on this. + Specifically, the installation folder is extremely likely to be somewhere else when DFHack is installed from Steam. + Always use this function to get the DFHack installation directory path instead of hardcoding it. + +* ``dfhack.getConfigPath()`` + + Returns the DFHack config directory path (the folder where user-specific configuration files are stored). + This is currently the ``dfhack-config`` folder within the DF installation, but you should not rely on this as it is likely to change in the future. + Always use this function to get the DFHack config directory path instead of hardcoding it. + Avoid storing this value in a long-lived variable, as it's possible that in future versions of DFHack, it may be possible for the config directory to be changed at runtime. * ``dfhack.getSavePath()`` @@ -1423,6 +1440,57 @@ Job module Returns the job's description, as seen in the Units and Jobs screens. +* ``dfhack.job.getManagerOrderName(manager_order)`` + + Returns the manager order's description, as seen in the Work orders screen. + +Hotkey module +------------- + +* ``dfhack.hotkey.addKeybind(keyspec, command)`` + + Creates a new keybind with the provided keyspec (see the `keybinding` documentation + for details on format). + Returns false on failure to create keybind. + +* ``dfhack.hotkey.removeKeybind(keyspec, [match_focus=true, command])`` + + Removes keybinds matching the provided keyspec. + If match_focus is set, the focus portion of the keyspec is matched against. + If command is provided and not an empty string, the command is matched against. + Returns false if no keybinds were removed. + +* ``dfhack.hotkey.listActiveKeybinds()`` + + Returns a list of keybinds active within the current context. + The items are tables with the following attributes: + :spec: The keyspec for the hotkey + :command: The command the hotkey runs when pressed + +* ``dfhack.hotkey.listAllKeybinds()`` + + Returns a list of all keybinds currently registered. + The items are tables with the following attributes: + :spec: The keyspec for the hotkey + :command: The command the hotkey runs when pressed + +* ``dfhack.hotkey.requestKeybindingInput([cancel=false])`` + + Enqueues or cancels a request that the next hotkey-compatible input is saved + and not processed, retrievable with ``dfhack.hotkey.getKeybindingInput()``. + If cancel is true, any current request is cancelled. + +* ``dfhack.hotkey.getKeybindingInput()`` + + Reads the latest saved keybind input that was requested. + Returns a keyspec string for the input, or nil if no input has been saved. + +* ``dfhack.hotkey.isDisruptiveKeybind(keyspec)`` + + Determines if the provided keyspec could be disruptive to the game experience. + This includes the majority of standard characters and special keys such as escape, + backspace, and return when lacking modifiers other than Shift. + Units module ------------ @@ -2432,9 +2500,30 @@ Maps module Removes an aquifer from the given tile position. Returns *true* or *false* depending on success. +* ``dfhack.maps.addMaterialSpatter(pos, mat, matg, state, amount)`` + + Adds a material spatter to the specified map tile. If the tile is already + full of that spatter, returns the amount left over. + + Specifying a state of -1 (None) will automatically choose either Solid, + Liquid, or Gas based on the material properties and the tile temperature. + +* ``dfhack.maps.addItemSpatter(pos, i_type, i_subtype, subcat1, subcat2, print_variant, amount)`` + + Adds an item spatter to the specified map tile. If the tile is already + full of that spatter, returns the amount left over. + + For plant growths, specifying a print_variant of -1 will automatically + choose an appropriate value. For other item types, this field is ignored. + Burrows module -------------- +* ``dfhack.burrows.getName(burrow)`` + + Returns the name of the burrow. + If the burrow has no set name, returns the same placeholder name that DF would show in the UI. + * ``dfhack.burrows.findByName(name[, ignore_final_plus])`` Returns the burrow pointer or *nil*. if ``ignore_final_plus`` is ``true``, @@ -2783,12 +2872,9 @@ Common parameters to these functions include: * ``x``, ``y``: screen coordinates in tiles; the upper left corner of the screen is ``x = 0, y = 0`` * ``pen``: a `pen object ` -* ``map``: a boolean indicating whether to draw to a separate map buffer - (defaults to false, which is suitable for off-map text or a screen that hides - the map entirely). Note that only third-party plugins like TWBT currently - implement a separate map buffer. If no such plugins are enabled, passing - ``true`` has no effect. However, this parameter should still be used to ensure - that scripts work properly with such plugins. +* ``map``: a boolean (defaults to false) indicating whether to draw to a + separate map buffer. The Steam version uses separate map buffers with square + tiles for for all types of maps (i.e. fort, region, and world). Functions: @@ -2814,15 +2900,31 @@ Functions: * ``dfhack.screen.paintTile(pen,x,y[,char[,tile[,map]]])`` Paints a tile using given parameters. `See below ` for a - description of ``pen``. + description of ``pen``. The map argument is only supported for local maps + (i.e. fort mode and adventure mode outside of fast travel). The ``char`` and + ``tile`` arguments allow overriding the respective parts of the ``pen`` + without constructing a new pen beforehand. Returns *false* on error, e.g., if coordinates are out of bounds +* ``dfhack.screen.paintMapPortTile(pen,x,y[,char[,tile]])`` + + Paints a tile using given parameters onto the interface texpos layer of a map + port (e.g., the world map or the zoomed-in map for embark selection). The + ``char`` and ``tile`` arguments work as above. + * ``dfhack.screen.readTile(x,y[,map])`` Retrieves the contents of the specified tile from the screen buffers. Returns a `pen object `, or *nil* if invalid or TrueType. +* ``dfhack.screen.readMapPortTile(x,y)`` + + Retrieves the contents of the specified tile from the screen buffers. Returns + a `pen object `, or *nil* if invalid. + + For now only looks at the ``sites`` textpos layer. + * ``dfhack.screen.paintString(pen,x,y,text[,map])`` Paints the string starting at *x,y*. Uses the string characters @@ -3487,7 +3589,7 @@ and are only documented here for completeness: * ``dfhack.internal.getModifiers()`` Returns the state of the keyboard modifier keys in a table of string -> - boolean. The keys are ``ctrl``, ``shift``, and ``alt``. + boolean. The keys are ``ctrl``, ``shift``, ``super``, and ``alt``. * ``dfhack.internal.getSuppressDuplicateKeyboardEvents()`` * ``dfhack.internal.setSuppressDuplicateKeyboardEvents(suppress)`` @@ -5678,7 +5780,7 @@ TextArea Functions: * ``textarea:getText()`` Returns the current text content of the ``TextArea`` widget as a string. - "\n" characters (``string.char(10)``) should be interpreted as new lines + ``\n`` characters (``string.char(10)``) should be interpreted as new lines * ``textarea:setText(text)`` @@ -6267,12 +6369,27 @@ This is a specialized subclass of CycleHotkeyLabel that has two options: ``On`` (with a value of ``true``) and ``Off`` (with a value of ``false``). The ``On`` option is rendered in green. +ConfigureButton class +--------------------- + +A 3x1 tile button with a gear symbol on it, intended to represent a configure +icon. Clicking on the icon will run the given callback. The graphics can also +be overridden to create custom buttons. + +It has the following attributes: + +:on_click: The function to run when the icon is clicked. +:pen_left: Pen or function returning a pen to overwrite the left tile of the button. +:pen_center: As above, but for the center tile (gear symbol). +:pen_right: As above, but for the right tile. + HelpButton class ---------------- -A 3x1 tile button with a question mark on it, intended to represent a help -icon. Clicking on the icon will launch `gui/launcher` with a given command -string, showing the help text for that command. +Subclass of ConfigureButton; a 3x1 tile button with a question mark on it, +intended to represent a help icon. Clicking on the icon will launch +`gui/launcher` with a given command string, showing the help text for that +command. It has the following attributes: @@ -6282,15 +6399,23 @@ It also sets the ``frame`` attribute so the button appears in the upper right corner of the parent, but you can override this to your liking if you want a different position. -ConfigureButton class ---------------------- +RadioButton class +----------------- -A 3x1 tile button with a gear mark on it, intended to represent a configure -icon. Clicking on the icon will run the given callback. +Subclass of ConfigureButton; a 3x1 tile button that resembles a radio button +(or check box in ASCII mode), identical to the ones found in +`gui/control-panel`. Clicking on the button will toggle its enabled state. It has the following attributes: -:on_click: The function on run when the icon is clicked. +:initial_state: Whether to start in the ``true`` or ``false`` state. Defaults to ``true``. +:on_change: Callback to call when state changes, including initialization. Called as ``on_change(val)``. + +It implements the following method: + +* ``RadioButton:setState(val)`` + + Sets the state to boolean ``val`` and calls ``on_change`` (if defined). BannerPanel class ----------------- @@ -6447,7 +6572,8 @@ Filter behavior: By default, the filter matches substrings that start at the beginning of a word (or after any punctuation). You can instead configure filters to match any -substring across the full text with a command like:: +substring across the full text by setting ``FILTER_FULL_TEXT`` in `gui/control-panel` +or set it for the session by running a command like:: :lua require('utils').FILTER_FULL_TEXT=true @@ -7461,6 +7587,15 @@ Importing scripts --@ module = true + In order to be recognized, this line **must** begin with ``--@`` with no + whitespace characters before it:: + + --@ module = true OK + --@module = true OK + -- @module = true NOT OK (no --@ found due to space after --) + --@module = true NOT OK (leading space, --@ is not at the beginning of the line) + ---@module = true NOT OK (leading dash, --@ is not at the beginning of the line) + 2. Include a check for ``dfhack_flags.module``, and avoid running any code that has side-effects if this flag is true. For instance:: diff --git a/docs/dev/compile/Compile.rst b/docs/dev/compile/Compile.rst index 3eea95d880..2ad2c52efb 100644 --- a/docs/dev/compile/Compile.rst +++ b/docs/dev/compile/Compile.rst @@ -442,3 +442,14 @@ a command starting with ``cmake .. -G Ninja`` on Linux and macOS, following the instructions in the sections above. CMake should automatically locate files that you placed in ``CMake/downloads``, and use them instead of attempting to download them. + +In addition, some packages used by DFHack are managed using CMake's ``FetchContent`` +feature, which requires an online connection during builds. The simplest way to address +this is to have a connection during the first build (during which CMake will download the +dependencies), and then to use CMake's ``FETCHCONTENT_FULLY_DISCONNECTED`` or +``FETCHCONTENT_UPDATES_DISCONNECTED`` defines to control how CMake manages cached +dependencies. If you need even the first-time build be an offline build, you will need +to provide a CMake dependency provider. We do not provide one, but CMake's own documentation +includes a simple provider. For more information about CMake's ``FetchContent`` feature +and how to use it in offline builds, see the +`CMake documentation `_. diff --git a/docs/dev/compile/Options.rst b/docs/dev/compile/Options.rst index b314e9db27..462fd1f1ca 100644 --- a/docs/dev/compile/Options.rst +++ b/docs/dev/compile/Options.rst @@ -136,16 +136,26 @@ Usage:: Documentation ============= -If you need to build documentation. Documentation can be built as HTML, and PDF, -but there are also plain text files generated for in-game. +If you need to build `documentation `. -Variable: ``BUILD_DOCS`` +.. note:: + + These options are primarily useful for verifying that the end-to-end process + for building and packaging the documentation is working as expected. For + iterating on documentation changes, `faster alternatives ` are + available. + +Variables: + +* ``BUILD_DOCS``: enables the default documentation build +* ``BUILD_DOCS_NO_HTML``: disables the HTML documentation build (only builds the text documentation used in-game) Usage:: cmake .. -DBUILD_DOCS:bool=ON cmake .. -DBUILD_DOCS=1 - + cmake .. -DBUILD_DOCS_NO_HTML:bool=ON + cmake .. -DBUILD_DOCS_NO_HTML=1 The generated documentation is stored in ``docs/html`` and ``docs/text`` (respectively) in the root DFHack folder, and they will both be installed to ``hack/docs`` when you diff --git a/docs/dev/data-identity.rst b/docs/dev/data-identity.rst index 855a6db26c..ae7076941d 100644 --- a/docs/dev/data-identity.rst +++ b/docs/dev/data-identity.rst @@ -123,18 +123,12 @@ Type identity object lifetime and mutability ============================================ *Most* instances of ``type_identity`` are statically constructed and immutable and are thus ``const static`` when constructed. -All ``type_identity`` pointers should be declared ``const``. -Due to the use of lazy initialization, ``compound_identity`` and its subclasses have a few fields that are marked as ``mutable`` -(so that the ``parent`` and ``child`` members can be updated as additional instances are constructed). -In addition, ``virtual_identity`` has two fields that are ``mutable`` due to the dual use of this type to implement -DFHack's vmethod interpose system. -Due to this dual purpose, it is important that there be at most one ``virtual_identity`` object per virtual class, -although this is not enforced at present. +All ``type_identity`` pointers should be declared ``const``. Due to ``virtual_identity``'s role in implementing +DFHack's vmethod interpose system, it is important that there be at most one ``virtual_identity`` object per virtual class. Having more than one ``struct_identity`` object for the same type might also potentially lead to misoperation. In general, there should be a one to one correspondence between ``type_identity`` objects and C++ types -(with the special case that ``global_identity`` has no corresponding type). -As far as we know, for any type other than ``virtual_identity``, +(with the special case that ``global_identity`` has no corresponding type). As far as we know, for any type other than ``virtual_identity``, violations of this constraint will not lead to misoperation, but this constraint should not be lightly violated. The Lua/C++ interface does, in a handful of places, assume that it can compare ``type_identity`` pointers to determine if they reference the same type, but as far as we know all of these instances will fall diff --git a/docs/dev/github-workflows.rst b/docs/dev/github-workflows.rst index fa1c36b66c..ea71e1a2b6 100644 --- a/docs/dev/github-workflows.rst +++ b/docs/dev/github-workflows.rst @@ -46,8 +46,8 @@ tuned our build and test workflows to minimize spurious cache misses and keep the fast path fast. Caches are namespaced by key prefixes, and we have one key prefix per build -context. For example, release builds on gcc-10 are kept in one cache namespace, -whereas test builds on gcc-10 are kept separate. MSVC release and test builds +context. For example, release builds on gcc-11 are kept in one cache namespace, +whereas test builds on gcc-11 are kept separate. MSVC release and test builds similarly have their own namespaces. Each cache has a maximum size that is enforced by the business logic that writes the cache data. diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index a5b39c529b..dae03c3aba 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -2298,8 +2298,10 @@ Symbol Type Properties ``ek`` kiln ``en`` magma kiln ``ib`` ballista +``it`` bolt thrower ``ic`` catapult ``Cw`` wall +``CW`` reinforced wall ``Cf`` floor ``Cr`` ramp ``Cu`` up stair diff --git a/docs/plugins/autofarm.rst b/docs/plugins/autofarm.rst index 8896c3117c..0fe98f2b8b 100644 --- a/docs/plugins/autofarm.rst +++ b/docs/plugins/autofarm.rst @@ -24,9 +24,7 @@ Usage Sets thresholds of individual plant types. You can find the identifiers for the crop types in your world by running the -following command:: - - lua "for _,plant in ipairs(df.global.world.raws.plants.all) do if plant.flags.SEED then print(plant.id) end end" +following command: ``getplants -f`` Examples -------- diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index c00569365c..01c864f7cc 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -204,6 +204,18 @@ other available items (or from items produced in the future if not all items are available yet). If there are multiple item types to choose for the current building, one dialog will appear per item type. +Queueing work orders +-------------------- + +If you are planning a building but do not have the required items in stock, you can +automatically queue a manager work order to produce the missing quantity. After +selecting your desired item types and filters, press :kbd:`Ctrl`:kbd:`q` (or click +"Queue order") to generate a work order. + +`buildingplan` will attempt to automatically determine the correct job (e.g. making +a wooden bed if you are planning a bed) and will respect the material categories +you have selected in your filters. + Building status --------------- @@ -237,3 +249,8 @@ usual) unless freed via the ``Free`` buttons on the ``Show items`` tab on both buildings. This will remove the mechanism from the building and drop it onto the ground, allowing it to be reused elsewhere. There is an option to auto-free mechanisms when unlinking to perform this step automatically. + +For any linked building that is a lever, a ``Pull`` button also appears next to it +on the ``Show linked buildings`` tab, with a glyph showing the lever's current +position. Clicking it queues a high-priority ("do now") pull-lever job without +having to navigate to the lever itself; click it again to cancel the job. diff --git a/docs/plugins/edgescroll.rst b/docs/plugins/edgescroll.rst new file mode 100644 index 0000000000..4522ff36bf --- /dev/null +++ b/docs/plugins/edgescroll.rst @@ -0,0 +1,13 @@ +edgescroll +========== + +.. dfhack-tool:: + :summary: Scroll the game world and region maps when the mouse reaches the window border. + :tags: interface + +Usage +----- + +:: + + enable edgescroll diff --git a/docs/plugins/tweak.rst b/docs/plugins/tweak.rst index ae42aecac7..c369960344 100644 --- a/docs/plugins/tweak.rst +++ b/docs/plugins/tweak.rst @@ -45,6 +45,9 @@ Commands Fixes crafted items not wearing out over time (:bug:`6003`). With this tweak, items made from cloth and leather will gain a level of wear every 20 in-game years. +``drawbridge-tiles`` + Makes raising bridges in ASCII mode render with different tiles when they + are raised. ``eggs-fertile`` Displays an indicator on fertile eggs. ``fast-heat`` diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 895ae6eb86..d87e20531e 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -48,6 +48,7 @@ set(MAIN_HEADERS include/DFHackVersion.h include/BitArray.h include/ColorText.h + include/Commands.h include/Console.h include/Core.h include/CoreDefs.h @@ -58,15 +59,15 @@ set(MAIN_HEADERS include/DebugManager.h include/Error.h include/Export.h + include/Format.h include/Hooks.h include/LuaTools.h include/LuaWrapper.h include/MemAccess.h include/Memory.h include/MiscUtils.h - include/Module.h include/MemAccess.h - include/ModuleFactory.h + include/MemoryPatcher.h include/PluginLua.h include/PluginManager.h include/PluginStatics.h @@ -88,6 +89,7 @@ set(MAIN_HEADERS_WINDOWS set(MAIN_SOURCES Core.cpp ColorText.cpp + Commands.cpp CompilerWorkAround.cpp DataDefs.cpp DataIdentity.cpp @@ -100,6 +102,7 @@ set(MAIN_SOURCES LuaApi.cpp DataStatics.cpp DataStaticsCtor.cpp + MemoryPatcher.cpp MiscUtils.cpp Types.cpp PluginManager.cpp @@ -126,7 +129,6 @@ endif() set(MAIN_SOURCES_WINDOWS ${CONSOLE_SOURCES} - Hooks.cpp ) if(WIN32) @@ -136,6 +138,7 @@ endif() set(MAIN_SOURCES_LINUX ${CONSOLE_SOURCES} + Crashlog.cpp ) set(MAIN_SOURCES_DARWIN @@ -152,9 +155,9 @@ set(MODULE_HEADERS include/modules/Designations.h include/modules/EventManager.h include/modules/Filesystem.h - include/modules/Graphic.h include/modules/Gui.h include/modules/GuiHooks.h + include/modules/Hotkey.h include/modules/Items.h include/modules/Job.h include/modules/Kitchen.h @@ -183,8 +186,8 @@ set(MODULE_SOURCES modules/Designations.cpp modules/EventManager.cpp modules/Filesystem.cpp - modules/Graphic.cpp modules/Gui.cpp + modules/Hotkey.cpp modules/Items.cpp modules/Job.cpp modules/Kitchen.cpp @@ -310,8 +313,6 @@ endif() # Compilation -add_definitions(-DBUILD_DFHACK_LIB) - if(UNIX) if(CONSOLE_NO_CATCH) add_definitions(-DCONSOLE_NO_CATCH) @@ -319,12 +320,12 @@ if(UNIX) endif() if(APPLE) - set(PROJECT_LIBS dl dfhack-md5 ${DFHACK_TINYXML}) + set(PROJECT_LIBS dl dfhack-md5 ${FMTLIB} ${DFHACK_TINYXML}) elseif(UNIX) - set(PROJECT_LIBS rt dl dfhack-md5 ${DFHACK_TINYXML}) + set(PROJECT_LIBS rt dl dfhack-md5 ${FMTLIB} ${DFHACK_TINYXML}) else(WIN32) # FIXME: do we really need psapi? - set(PROJECT_LIBS psapi dbghelp dfhack-md5 ${DFHACK_TINYXML}) + set(PROJECT_LIBS psapi dbghelp dfhack-md5 ${FMTLIB} ${DFHACK_TINYXML}) endif() set(VERSION_SRCS DFHackVersion.cpp) @@ -365,6 +366,7 @@ if(EXISTS ${dfhack_SOURCE_DIR}/.git/index AND EXISTS ${dfhack_SOURCE_DIR}/.git/m endif() add_library(dfhack SHARED ${PROJECT_SOURCES}) +target_compile_definitions(dfhack PRIVATE BUILD_DFHACK_LIB) target_include_directories(dfhack PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/proto) get_target_property(xlsxio_INCLUDES xlsxio_read_STATIC INTERFACE_INCLUDE_DIRECTORIES) @@ -373,6 +375,7 @@ add_dependencies(dfhack generate_proto_core) add_dependencies(dfhack generate_headers) add_library(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp Error.cpp ${PROJECT_PROTO_SRCS} ${CONSOLE_SOURCES}) +target_compile_definitions(dfhack-client PRIVATE BUILD_DFHACK_LIB) target_include_directories(dfhack-client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/proto) add_dependencies(dfhack-client dfhack) @@ -383,16 +386,16 @@ add_executable(binpatch binpatch.cpp) target_link_libraries(binpatch dfhack-md5) if(WIN32) - set_target_properties(dfhack PROPERTIES OUTPUT_NAME "dfhooks_dfhack" ) set_target_properties(dfhack PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) set_target_properties(dfhack-client PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) else() set_target_properties(dfhack PROPERTIES COMPILE_FLAGS "-include Export.h" ) set_target_properties(dfhack-client PROPERTIES COMPILE_FLAGS "-include Export.h" ) - add_library(dfhooks_dfhack SHARED Hooks.cpp) - target_link_libraries(dfhooks_dfhack dfhack) endif() +add_library(dfhooks_dfhack SHARED Hooks.cpp) +target_link_libraries(dfhooks_dfhack PUBLIC dfhack ${FMTLIB}) + # effectively disables debug builds... set_target_properties(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) @@ -419,7 +422,7 @@ endif() target_link_libraries(dfhack protobuf-lite clsocket lua jsoncpp_static dfhack-version ${PROJECT_LIBS}) set_target_properties(dfhack PROPERTIES INTERFACE_LINK_LIBRARIES "") -target_link_libraries(dfhack-client protobuf-lite clsocket jsoncpp_static) +target_link_libraries(dfhack-client protobuf-lite clsocket jsoncpp_static ${FMTLIB}) if(WIN32) target_link_libraries(dfhack-client dbghelp) endif() @@ -442,11 +445,13 @@ if(UNIX) install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run DESTINATION .) endif() - install(TARGETS dfhooks_dfhack - LIBRARY DESTINATION . - RUNTIME DESTINATION .) endif() +install(TARGETS dfhooks_dfhack + LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} + RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) + + # install the main lib install(TARGETS dfhack LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} @@ -456,6 +461,12 @@ install(TARGETS dfhack-run dfhack-client binpatch LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) +file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dfhooks_dfhack.ini + CONTENT "${DFHACK_DATA_DESTINATION}/$") + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dfhooks_dfhack.ini + DESTINATION .) + endif(BUILD_LIBRARY) # install the offset file diff --git a/library/ColorText.cpp b/library/ColorText.cpp index 8dc5cc5dcf..a101d795a9 100644 --- a/library/ColorText.cpp +++ b/library/ColorText.cpp @@ -35,24 +35,12 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -#include -#include -#include -#include -#include -#include #include +#include +#include #include "ColorText.h" -#include "MiscUtils.h" - -#include -#include -#include -#include -using namespace std; using namespace DFHack; bool color_ostream::log_errors_to_stderr = false; @@ -81,7 +69,7 @@ void color_ostream::end_batch() flush_proxy(); } -color_ostream::color_ostream() : ostream(new buffer(this)), cur_color(COLOR_RESET) +color_ostream::color_ostream() : std::ostream(new buffer(this)), cur_color(COLOR_RESET) { // } @@ -91,54 +79,6 @@ color_ostream::~color_ostream() delete buf(); } -void color_ostream::print(const char *format, ...) -{ - va_list args; - va_start(args, format); - vprint(format, args); - va_end(args); -} - -void color_ostream::vprint(const char *format, va_list args) -{ - std::string str = stl_vsprintf(format, args); - - if (!str.empty()) { - flush_buffer(false); - add_text(cur_color, str); - if (str[str.size()-1] == '\n') - flush_proxy(); - } -} - -void color_ostream::printerr(const char * format, ...) -{ - va_list args; - va_start(args, format); - vprinterr(format, args); - va_end(args); -} - -void color_ostream::vprinterr(const char *format, va_list args) -{ - color_value save = cur_color; - - if (log_errors_to_stderr) - { - va_list args1; - va_copy(args1, args); - vfprintf(stderr, format, args1); - va_end(args1); - } - - color(COLOR_LIGHTRED); - va_list args2; - va_copy(args2, args); - vprint(format, args2); - va_end(args2); - color(save); -} - void color_ostream::color(color_value c) { if (c == cur_color) diff --git a/library/Commands.cpp b/library/Commands.cpp new file mode 100644 index 0000000000..b19ed68bb4 --- /dev/null +++ b/library/Commands.cpp @@ -0,0 +1,589 @@ + +#include "Commands.h" + +#include "ColorText.h" +#include "Core.h" +#include "CoreDefs.h" +#include "LuaTools.h" +#include "PluginManager.h" +#include "RemoteTools.h" + +#include "modules/Gui.h" +#include "modules/Hotkey.h" +#include "modules/World.h" + +#include "df/viewscreen_new_regionst.h" + +#include +#include +#include + + +namespace DFHack +{ + command_result Commands::help(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (!parts.size()) + { + if (con.is_console()) + { + con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n" + "Some basic editing capabilities are included (single-line text editing).\n" + "The console also has a command history - you can navigate it with Up and Down keys.\n" + "On Windows, you may have to resize your console window. The appropriate menu is accessible\n" + "by clicking on the program icon in the top bar of the window.\n\n"); + } + con.print("Here are some basic commands to get you started:\n" + " help|?|man - This text.\n" + " help - Usage help for the given plugin, command, or script.\n" + " tags - List the tags that the DFHack tools are grouped by.\n" + " ls|dir [] - List commands, optionally filtered by a tag or substring.\n" + " Optional parameters:\n" + " --notags: skip printing tags for each command.\n" + " --dev: include commands intended for developers and modders.\n" + " cls|clear - Clear the console.\n" + " fpause - Force DF to pause.\n" + " die - Force DF to close immediately, without saving.\n" + " keybinding - Modify bindings of commands to in-game key shortcuts.\n" + "\n" + "See more commands by running 'ls'.\n\n" + ); + + con.print("DFHack version {}\n", dfhack_version_desc()); + } + else + { + DFHack::help_helper(con, parts[0]); + } + return CR_OK; + } + + command_result Commands::load(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + bool all = false; + bool load = (first == "load"); + bool unload = (first == "unload"); + bool reload = (first == "reload"); + auto plug_mgr = core.getPluginManager(); + if (parts.size()) + { + for (const auto& p : parts) + { + if (p.size() && p[0] == '-') + { + if (p.find('a') != std::string::npos) + all = true; + } + } + auto ret = CR_OK; + if (all) + { + if (load && !plug_mgr->loadAll()) + ret = CR_FAILURE; + else if (unload && !plug_mgr->unloadAll()) + ret = CR_FAILURE; + else if (reload && !plug_mgr->reloadAll()) + ret = CR_FAILURE; + } + else + { + for (auto& p : parts) + { + if (p.empty() || p[0] == '-') + continue; + if (load && !plug_mgr->load(p)) + ret = CR_FAILURE; + else if (unload && !plug_mgr->unload(p)) + ret = CR_FAILURE; + else if (reload && !plug_mgr->reload(p)) + ret = CR_FAILURE; + } + } + if (ret != CR_OK) + con.printerr("{} failed\n", first.c_str()); + return ret; + } + else + { + con.printerr("{}: no arguments\n", first.c_str()); + return CR_FAILURE; + } + + } + + command_result Commands::enable(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + CoreSuspender suspend; + bool enable = (first == "enable"); + auto plug_mgr = core.getPluginManager(); + + if (parts.size()) + { + command_result res{CR_FAILURE}; + + for (auto& part_ : parts) + { + // have to copy to modify as passed argument is const + std::string part(part_); + + if (has_backslashes(part)) + { + con.printerr("Replacing backslashes with forward slashes in \"{}\"\n", part); + replace_backslashes_with_forwardslashes(part); + } + + auto alias = core.GetAliasCommand(part, true); + + Plugin* plug = (*plug_mgr)[alias]; + + if (!plug) + { + std::filesystem::path lua = core.findScript(part + ".lua"); + if (!lua.empty()) + { + res = core.enableLuaScript(con, part, enable); + } + else + { + res = CR_NOT_FOUND; + con.printerr("No such plugin or Lua script: {}\n", part); + } + } + else if (!plug->can_set_enabled()) + { + res = CR_NOT_IMPLEMENTED; + con.printerr("Cannot {} plugin: {}\n", first, part); + } + else + { + res = plug->set_enabled(con, enable); + + if (res != CR_OK || plug->is_enabled() != enable) + con.printerr("Could not {} plugin: {}\n", first, part); + } + } + + return res; + } + else + { + for (auto& [key, plug] : *plug_mgr) + { + if (!plug) + continue; + if (!plug->can_be_enabled()) continue; + + con.print( + "{:>21} {:<3}{}\n", + (key + ":").c_str(), + plug->is_enabled() ? "on" : "off", + plug->can_set_enabled() ? "" : " (controlled internally)" + ); + } + + Lua::CallLuaModuleFunction(con, "script-manager", "list"); + + return CR_OK; + } + } + + command_result Commands::plug(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + constexpr auto header_format = "{:30} {:10} {:4} {:8}\n"; + constexpr auto row_format = header_format; + + con.print(header_format, "Name", "State", "Cmds", "Enabled"); + + auto plug_mgr = core.getPluginManager(); + + plug_mgr->refresh(); + for (auto& [key, plug] : *plug_mgr) + { + if (!plug) + continue; + + if (parts.size() && std::ranges::find(parts, key) == parts.end()) + continue; + + color_value color; + switch (plug->getState()) + { + case Plugin::PS_LOADED: + color = COLOR_RESET; + break; + case Plugin::PS_UNLOADED: + case Plugin::PS_UNLOADING: + color = COLOR_YELLOW; + break; + case Plugin::PS_LOADING: + color = COLOR_LIGHTBLUE; + break; + case Plugin::PS_BROKEN: + color = COLOR_LIGHTRED; + break; + default: + color = COLOR_LIGHTMAGENTA; + break; + } + con.color(color); + con.print(row_format, + plug->getName(), + Plugin::getStateDescription(plug->getState()), + plug->size(), + (plug->can_be_enabled() + ? (plug->is_enabled() ? "enabled" : "disabled") + : "n/a") + ); + con.color(COLOR_RESET); + } + + return CR_OK; + } + + command_result Commands::type(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + auto plug_mgr = core.getPluginManager(); + + if (!parts.size()) + { + con.printerr("type: no argument\n"); + return CR_WRONG_USAGE; + } + con << parts[0]; + bool builtin = is_builtin(con, parts[0]); + std::filesystem::path lua_path = core.findScript(parts[0] + ".lua"); + Plugin* plug = plug_mgr->getPluginByCommand(parts[0]); + if (builtin) + { + con << " is a built-in command"; + con << std::endl; + } + else if (core.IsAlias(parts[0])) + { + con << " is an alias: " << core.GetAliasCommand(parts[0]) << std::endl; + } + else if (plug) + { + con << " is a command implemented by the plugin " << plug->getName() << std::endl; + } + else if (!lua_path.empty()) + { + con << " is a Lua script: " << lua_path << std::endl; + } + else + { + con << " is not a recognized command." << std::endl; + plug = plug_mgr->getPluginByName(parts[0]); + if (plug) + con << "Plugin " << parts[0] << " exists and implements " << plug->size() << " commands." << std::endl; + return CR_FAILURE; + } + return CR_OK; + } + + command_result Commands::keybinding(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + using Hotkey::KeySpec; + auto hotkey_mgr = core.getHotkeyManager(); + std::string parse_error; + if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) { + const std::string& keystr = parts[1]; + if (parts[0] == "set") + hotkey_mgr->removeKeybind(keystr); + for (const auto& part : parts | std::views::drop(2) | std::views::reverse) { + auto spec = KeySpec::parse(keystr, &parse_error); + if (!spec.has_value()) { + con.printerr("{}\n", parse_error); + break; + } + if (!hotkey_mgr->addKeybind(spec.value(), part)) { + con.printerr("Invalid command: '{}'\n", part); + break; + } + } + } + else if (parts.size() >= 2 && parts[0] == "clear") { + for (const auto& part : parts | std::views::drop(1)) { + auto spec = KeySpec::parse(part, &parse_error); + if (!spec.has_value()) { + con.printerr("{}\n", parse_error); + } + if (!hotkey_mgr->removeKeybind(spec.value())) { + con.printerr("No matching keybinds to remove\n"); + break; + } + } + } + else if (parts.size() == 2 && parts[0] == "list") { + auto spec = KeySpec::parse(parts[1], &parse_error); + if (!spec.has_value()) { + con.printerr("{}\n", parse_error); + return CR_FAILURE; + } + std::vector list = hotkey_mgr->listKeybinds(spec.value()); + if (list.empty()) + con << "No bindings." << std::endl; + for (const auto& kb : list) + con << " " << kb << std::endl; + } + else + { + con << "Usage:\n" + << " keybinding list \n" + << " keybinding clear [@context]...\n" + << " keybinding set [@context] \"cmdline\" \"cmdline\"...\n" + << " keybinding add [@context] \"cmdline\" \"cmdline\"...\n" + << "Later adds, and earlier items within one command have priority.\n" + << "Key format: [Ctrl-][Alt-][Super-][Shift-](A-Z, 0-9, F1-F12, `, etc.).\n" + << "Context may be used to limit the scope of the binding, by\n" + << "requiring the current context to have a certain prefix.\n" + << "Current UI context is: \n" + << join_strings("\n", Gui::getCurFocus(true)) << std::endl; + } + return CR_OK; + } + + command_result Commands::alias(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (parts.size() >= 3 && (parts[0] == "add" || parts[0] == "replace")) + { + const std::string& name = parts[1]; + std::vector cmd(parts.begin() + 2, parts.end()); + if (!core.AddAlias(name, cmd, parts[0] == "replace")) + { + con.printerr("Could not add alias {} - already exists\n", name); + return CR_FAILURE; + } + } + else if (parts.size() >= 2 && (parts[0] == "delete" || parts[0] == "clear")) + { + if (!core.RemoveAlias(parts[1])) + { + con.printerr("Could not remove alias {}\n", parts[1]); + return CR_FAILURE; + } + } + else if (parts.size() >= 1 && (parts[0] == "list")) + { + auto aliases = core.ListAliases(); + for (auto p : aliases) + { + con << p.first << ": " << join_strings(" ", p.second) << std::endl; + } + } + else + { + con << "Usage: " << std::endl + << " alias add|replace " << std::endl + << " alias delete|clear " << std::endl + << " alias list" << std::endl; + } + + return CR_OK; + } + + command_result Commands::fpause(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (auto scr = Gui::getViewscreenByType()) + { + if (scr->doing_mods || scr->doing_simple_params || scr->doing_params) + { + con.printerr("Cannot pause now.\n"); + return CR_FAILURE; + } + scr->abort_world_gen_dialogue = true; + } + else + { + World::SetPauseState(true); + } + con.print("The game was forced to pause!\n"); + return CR_OK; + } + + command_result Commands::clear(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (con.can_clear()) + { + con.clear(); + return CR_OK; + } + else + { + con.printerr("No console to clear, or this console does not support clearing.\n"); + return CR_NEEDS_CONSOLE; + } + } + + command_result Commands::kill_lua(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + bool force = std::ranges::any_of(parts, [] (const std::string& part) { return part == "force"; }); + if (!Lua::Interrupt(force)) + { + con.printerr( + "Failed to register hook. This can happen if you have" + " lua profiling or coverage monitoring enabled. Use" + " 'kill-lua force' to force, but this may disable" + " profiling and coverage monitoring.\n"); + return CR_FAILURE; + } + return CR_OK; + } + + command_result Commands::script(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (parts.size() == 1) + { + core.loadScriptFile(con, std::filesystem::weakly_canonical(std::filesystem::path{parts[0]}), false); + return CR_OK; + } + else + { + con << "Usage:" << std::endl + << " script " << std::endl; + return CR_WRONG_USAGE; + } + } + + command_result Commands::show(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (!core.getConsole().show()) + { + con.printerr("Could not show console\n"); + return CR_FAILURE; + } + return CR_OK; + } + + command_result Commands::hide(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (!core.getConsole().hide()) + { + con.printerr("Could not hide console\n"); + return CR_FAILURE; + } + return CR_OK; + } + + command_result Commands::sc_script(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (parts.empty() || parts[0] == "help" || parts[0] == "?") + { + con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << std::endl; + con << "Valid event names (SC_ prefix is optional):" << std::endl; + for (int i = SC_WORLD_LOADED; i <= SC_UNPAUSED; i++) + { + std::string name = sc_event_name((state_change_event)i); + if (name != "SC_UNKNOWN") + con << " " << name << std::endl; + } + return CR_OK; + } + else if (parts[0] == "list") + { + std::string event_name = parts.size() >= 2 ? parts[1] : ""; + if (event_name.size() && sc_event_id(event_name) == SC_UNKNOWN) + { + con << "Unrecognized event name: " << parts[1] << std::endl; + return CR_WRONG_USAGE; + } + for (const auto& state_script : core.getStateChangeScripts()) + { + if (!parts[1].size() || (state_script.event == sc_event_id(parts[1]))) + { + con.print("{} ({}): {}{}\n", sc_event_name(state_script.event), + state_script.save_specific ? "save-specific" : "global", + state_script.save_specific ? "/raw/" : "/", + state_script.path); + } + } + return CR_OK; + } + else if (parts[0] == "add") + { + if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) + { + con << "Usage: sc-script add EVENT path-to-script [-save]" << std::endl; + return CR_WRONG_USAGE; + } + state_change_event evt = sc_event_id(parts[1]); + if (evt == SC_UNKNOWN) + { + con << "Unrecognized event: " << parts[1] << std::endl; + return CR_FAILURE; + } + bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); + StateChangeScript script(evt, parts[2], save_specific); + for (const auto& state_script : core.getStateChangeScripts()) + { + if (script == state_script) + { + con << "Script already registered" << std::endl; + return CR_FAILURE; + } + } + core.addStateChangeScript(script); + return CR_OK; + } + else if (parts[0] == "remove") + { + if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) + { + con << "Usage: sc-script remove EVENT path-to-script [-save]" << std::endl; + return CR_WRONG_USAGE; + } + state_change_event evt = sc_event_id(parts[1]); + if (evt == SC_UNKNOWN) + { + con << "Unrecognized event: " << parts[1] << std::endl; + return CR_FAILURE; + } + bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); + StateChangeScript tmp(evt, parts[2], save_specific); + if (core.removeStateChangeScript(tmp)) + { + return CR_OK; + } + else + { + con << "Unrecognized script" << std::endl; + return CR_FAILURE; + } + } + else + { + con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << std::endl; + return CR_WRONG_USAGE; + } + } + + command_result Commands::dump_rpc(color_ostream& con, Core& core, const std::string& first, const std::vector& parts) + { + if (parts.size() == 1) + { + std::ofstream file(parts[0]); + CoreService coreSvc; + coreSvc.dumpMethods(file); + + for (auto& it : *core.getPluginManager()) + { + Plugin* plug = it.second; + if (!plug) + continue; + + std::unique_ptr svc(plug->rpc_connect(con)); + if (!svc) + continue; + + file << "// Plugin: " << plug->getName() << std::endl; + svc->dumpMethods(file); + } + } + else + { + con << "Usage: devel/dump-rpc \"filename\"" << std::endl; + return CR_WRONG_USAGE; + } + return CR_OK; + } +} diff --git a/library/Console-windows.cpp b/library/Console-windows.cpp index 12a3e0c2eb..29d8f93c83 100644 --- a/library/Console-windows.cpp +++ b/library/Console-windows.cpp @@ -474,6 +474,10 @@ bool Console::init(bool) HMENU hm = GetSystemMenu(d->ConsoleWindow,false); DeleteMenu(hm, SC_CLOSE, MF_BYCOMMAND); + // force console code pages to utf-8 + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + // set the screen buffer to be big enough to let us scroll text GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); d->default_attributes = coninfo.wAttributes; @@ -512,6 +516,7 @@ bool Console::init(bool) // FIXME: looks awfully empty, doesn't it? bool Console::shutdown(void) { + assert(inited); std::lock_guard lock{*wlock}; FreeConsole(); inited = false; diff --git a/library/Core.cpp b/library/Core.cpp index a2b48c992d..9ff34386c8 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -22,38 +22,44 @@ must not be misrepresented as being the original software. distribution. */ +#include "Core.h" + #include "Internal.h" -#include "Error.h" -#include "MemAccess.h" -#include "Core.h" +#include "ColorText.h" +#include "Commands.h" +#include "Console.h" +#include "CoreDefs.h" #include "DataDefs.h" #include "Debug.h" -#include "Console.h" +#include "DFHackVersion.h" +#include "Error.h" +#include "Format.h" +#include "LuaTools.h" +#include "MemAccess.h" +#include "MemoryPatcher.h" +#include "MiscUtils.h" #include "MiscUtils.h" -#include "Module.h" -#include "VersionInfoFactory.h" -#include "VersionInfo.h" #include "PluginManager.h" -#include "ModuleFactory.h" #include "RemoteServer.h" #include "RemoteTools.h" -#include "LuaTools.h" -#include "DFHackVersion.h" -#include "md5wrapper.h" +#include "VersionInfo.h" +#include "VersionInfoFactory.h" #include "modules/DFSDL.h" #include "modules/DFSteam.h" #include "modules/EventManager.h" #include "modules/Filesystem.h" #include "modules/Gui.h" +#include "modules/Hotkey.h" +#include "modules/Persistence.h" #include "modules/Textures.h" #include "modules/World.h" -#include "modules/Persistence.h" -#include "df/init.h" #include "df/gamest.h" +#include "df/global_objects.h" #include "df/graphic.h" +#include "df/init.h" #include "df/interfacest.h" #include "df/plotinfost.h" #include "df/viewscreen_dwarfmodest.h" @@ -62,33 +68,52 @@ distribution. #include "df/viewscreen_loadgamest.h" #include "df/viewscreen_new_regionst.h" #include "df/viewscreen_savegamest.h" -#include "df/world.h" #include "df/world_data.h" +#include "df/world.h" -#include -#include -#include -#include -#include -#include +#include +#include #include -#include -#include -#include -#include +#include +#include #include +#include #include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include +#include +#include + +#include "md5wrapper.h" + #include +#include +#include -#ifdef _WIN32 +#include + +#ifdef WIN32 #define NOMINMAX +#define WIN32_LEAN_AND_MEAN #include +#include #endif #ifdef LINUX_BUILD @@ -97,29 +122,20 @@ distribution. using namespace DFHack; using namespace df::enums; + using df::global::init; using df::global::world; using std::string; // FIXME: A lot of code in one file, all doing different things... there's something fishy about it. -static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string *pfocus = NULL); -size_t loadScriptFiles(Core* core, color_ostream& out, const std::vector& prefix, const std::filesystem::path& folder); +static size_t loadScriptFiles(Core* core, color_ostream& out, std::span prefix, const std::filesystem::path& folder); namespace DFHack { - DBG_DECLARE(core, keybinding, DebugCategory::LINFO); DBG_DECLARE(core, script, DebugCategory::LINFO); - static const std::filesystem::path getConfigPath() - { - return Filesystem::getInstallDir() / "dfhack-config"; - }; - - static const std::filesystem::path getConfigDefaultsPath() - { - return Filesystem::getInstallDir() / "hack" / "data" / "dfhack-config-defaults"; - }; + Core* Core::active_instance = nullptr; class MainThread { public: @@ -200,7 +216,7 @@ uint32_t PerfCounters::getUnpausedFps() { struct CommandDepthCounter { - static const int MAX_DEPTH = 20; + static constexpr int MAX_DEPTH = 20; static thread_local int depth; CommandDepthCounter() { depth++; } ~CommandDepthCounter() { depth--; } @@ -210,7 +226,7 @@ thread_local int CommandDepthCounter::depth = 0; void Core::cheap_tokenise(std::string const& input, std::vector& output) { - std::string *cur = NULL; + std::string *cur = nullptr; size_t i = 0; // Check the first non-space character @@ -241,7 +257,7 @@ void Core::cheap_tokenise(std::string const& input, std::vector& ou { unsigned char c = input[i]; if (isspace(c)) { - cur = NULL; + cur = nullptr; } else { if (!cur) { output.push_back(""); @@ -273,50 +289,7 @@ struct IODATA PluginManager * plug_mgr; }; -// A thread function... for handling hotkeys. This is needed because -// all the plugin commands are expected to be run from foreign threads. -// Running them from one of the main DF threads will result in deadlock! -static void fHKthread(IODATA * iodata) -{ - Core * core = iodata->core; - PluginManager * plug_mgr = iodata->plug_mgr; - if(plug_mgr == 0 || core == 0) - { - std::cerr << "Hotkey thread has croaked." << std::endl; - return; - } - bool keep_going = true; - while(keep_going) - { - std::string stuff = core->getHotkeyCmd(keep_going); // waits on mutex! - if(!stuff.empty()) - { - color_ostream_proxy out(core->getConsole()); - - auto rv = core->runCommand(out, stuff); - - if (rv == CR_NOT_IMPLEMENTED) - out.printerr("Invalid hotkey command: '%s'\n", stuff.c_str()); - } - } -} - -struct sortable -{ - bool recolor; - std::string name; - std::string description; - //FIXME: Nuke when MSVC stops failing at being C++11 compliant - sortable(bool recolor_,const std::string& name_,const std::string & description_): recolor(recolor_), name(name_), description(description_){}; - bool operator <(const sortable & rhs) const - { - if( name < rhs.name ) - return true; - return false; - }; -}; - -static std::string dfhack_version_desc() +std::string DFHack::dfhack_version_desc() { std::stringstream s; s << Version::dfhack_version() << " "; @@ -330,56 +303,60 @@ static std::string dfhack_version_desc() return s.str(); } -static bool init_run_script(color_ostream &out, lua_State *state, const std::string& pcmd, std::vector& pargs) +static bool init_run_script(color_ostream &out, lua_State *state, const std::string_view pcmd, const std::span pargs) { if (!lua_checkstack(state, pargs.size()+10)) return false; Lua::PushDFHack(state); lua_getfield(state, -1, "run_script"); lua_remove(state, -2); - lua_pushstring(state, pcmd.c_str()); - for (auto& arg : pargs) + lua_pushlstring(state, pcmd.data(), pcmd.size()); + for (const auto& arg : pargs) lua_pushstring(state, arg.c_str()); return true; } -static command_result runLuaScript(color_ostream &out, std::string name, std::vector &args) +static command_result runLuaScript(color_ostream &out, const std::string_view name, const std::span args) { - using namespace std::placeholders; - auto init_fn = std::bind(init_run_script, _1, _2, name, args); + auto init_fn = [name, args](color_ostream& out, lua_State* state) -> bool { + return init_run_script(out, state, name, args); + }; + bool ok = Lua::RunCoreQueryLoop(out, DFHack::Core::getInstance().getLuaState(true), init_fn); return ok ? CR_OK : CR_FAILURE; } -static bool init_enable_script(color_ostream &out, lua_State *state, std::string& name, bool enable) +static bool init_enable_script(color_ostream &out, lua_State *state, const std::string_view name, bool enable) { if (!lua_checkstack(state, 4)) return false; Lua::PushDFHack(state); lua_getfield(state, -1, "enable_script"); lua_remove(state, -2); - lua_pushstring(state, name.c_str()); + lua_pushlstring(state, name.data(), name.size()); lua_pushboolean(state, enable); return true; } -static command_result enableLuaScript(color_ostream &out, std::string name, bool state) +command_result Core::enableLuaScript(color_ostream &out, const std::string_view name, bool enabled) { - using namespace std::placeholders; - auto init_fn = std::bind(init_enable_script, _1, _2, name, state); + auto init_fn = [name, enabled](color_ostream& out, lua_State* state) -> bool { + return init_enable_script(out, state, name, enabled); + }; + bool ok = Lua::RunCoreQueryLoop(out, DFHack::Core::getInstance().getLuaState(), init_fn); return ok ? CR_OK : CR_FAILURE; } -command_result Core::runCommand(color_ostream &out, const std::string &command) +command_result Core::runCommand(color_ostream& out, const std::string& command) { if (!command.empty()) { std::vector parts; - Core::cheap_tokenise(command,parts); - if(parts.size() == 0) + Core::cheap_tokenise(command, parts); + if (parts.size() == 0) return CR_NOT_IMPLEMENTED; std::string first = parts[0]; @@ -395,20 +372,23 @@ command_result Core::runCommand(color_ostream &out, const std::string &command) return CR_NOT_IMPLEMENTED; } -bool is_builtin(color_ostream &con, const std::string &command) { +bool DFHack::is_builtin(color_ostream& con, const std::string& command) +{ CoreSuspender suspend; auto L = DFHack::Core::getInstance().getLuaState(); Lua::StackUnwinder top(L); if (!lua_checkstack(L, 1) || - !Lua::PushModulePublic(con, L, "helpdb", "is_builtin")) { + !Lua::PushModulePublic(con, L, "helpdb", "is_builtin")) + { con.printerr("Failed to load helpdb Lua code\n"); return false; } Lua::Push(L, command); - if (!Lua::SafeCall(con, L, 1, 1)) { + if (!Lua::SafeCall(con, L, 1, 1)) + { con.printerr("Failed Lua call to helpdb.is_builtin.\n"); return false; } @@ -454,7 +434,7 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std:: { completed = possible[0]; //fprintf(stderr, "Autocompleted %s to %s\n", , ); - con.printerr("%s is not recognized. Did you mean %s?\n", first.c_str(), completed.c_str()); + con.printerr("{} is not recognized. Did you mean {}?\n", first, completed); return true; } @@ -463,7 +443,7 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std:: std::string out; for (size_t i = 0; i < possible.size(); i++) out += " " + possible[i]; - con.printerr("%s is not recognized. Possible completions:%s\n", first.c_str(), out.c_str()); + con.printerr("{} is not recognized. Possible completions:{}\n", first, out); return true; } @@ -522,7 +502,7 @@ void Core::getScriptPaths(std::vector *dest) if (save.size()) dest->emplace_back(df_pref_path / "save" / save / "scripts"); } - dest->emplace_back(df_install_path / "hack" / "scripts"); + dest->emplace_back(getHackPath() / "scripts"); for (auto & path : script_paths[2]) dest->emplace_back(path); for (auto & path : script_paths[1]) @@ -533,23 +513,31 @@ std::filesystem::path Core::findScript(std::string name) { std::vector paths; getScriptPaths(&paths); - for (auto it = paths.begin(); it != paths.end(); ++it) + for (auto& path : paths) { - std::filesystem::path path = std::filesystem::weakly_canonical(*it / name); - if (Filesystem::isfile(path)) - return path; + std::error_code ec; + auto raw_path = path / name; + std::filesystem::path load_path = std::filesystem::weakly_canonical(raw_path, ec); + if (ec) + { + con.printerr("Error loading '{}' ({})\n", raw_path, ec.message()); + continue; + } + + if (Filesystem::isfile(load_path)) + return load_path; } return {}; } -bool loadScriptPaths(color_ostream &out, bool silent = false) +bool Core::loadScriptPaths(color_ostream &out, bool silent) { std::filesystem::path filename{ getConfigPath() / "script-paths.txt" }; std::ifstream file(filename); if (!file) { if (!silent) - out.printerr("Could not load %s\n", filename.c_str()); + out.printerr("Could not load {}\n", filename); return false; } std::string raw; @@ -567,11 +555,11 @@ bool loadScriptPaths(color_ostream &out, bool silent = false) getline(ss, path); if (ch == '+' || ch == '-') { - if (!Core::getInstance().addScriptPath(path, ch == '+') && !silent) - out.printerr("%s:%i: Failed to add path: %s\n", filename.c_str(), line, path.c_str()); + if (!addScriptPath(path, ch == '+') && !silent) + out.printerr("{}:{}: Failed to add path: {}\n", filename, line, path); } else if (!silent) - out.printerr("%s:%i: Illegal character: %c\n", filename.c_str(), line, ch); + out.printerr("{}:{}: Illegal character: {}\n", filename, line, ch); } return true; } @@ -586,7 +574,7 @@ static void loadModScriptPaths(color_ostream &out) { DEBUG(script,out).print("final mod script paths:\n"); for (auto& path : mod_script_paths_str) { - DEBUG(script, out).print(" %s\n", path.c_str()); + DEBUG(script, out).print(" {}\n", path); mod_script_paths.push_back(std::filesystem::weakly_canonical(std::filesystem::path{ path })); } Core::getInstance().setModScriptPaths(mod_script_paths); @@ -608,7 +596,7 @@ static void sc_event_map_init() { } } -static state_change_event sc_event_id (std::string name) { +state_change_event DFHack::sc_event_id (std::string name) { sc_event_map_init(); auto it = state_change_event_map.find(name); if (it != state_change_event_map.end()) @@ -618,7 +606,7 @@ static state_change_event sc_event_id (std::string name) { return SC_UNKNOWN; } -static std::string sc_event_name (state_change_event id) { +std::string DFHack::sc_event_name (state_change_event id) { sc_event_map_init(); for (auto it = state_change_event_map.begin(); it != state_change_event_map.end(); ++it) { @@ -628,7 +616,7 @@ static std::string sc_event_name (state_change_event id) { return "SC_UNKNOWN"; } -void help_helper(color_ostream &con, const std::string &entry_name) { +void DFHack::help_helper(color_ostream &con, const std::string &entry_name) { ConditionalCoreSuspender suspend{}; if (!suspend) { @@ -676,14 +664,14 @@ void tags_helper(color_ostream &con, const std::string &tag) { } } -void ls_helper(color_ostream &con, const std::vector ¶ms) { +static void ls_helper(color_ostream &con, const std::span params) { std::vector filter; bool skip_tags = false; bool show_dev_commands = false; - std::string exclude_strs = ""; + std::string_view exclude_strs; bool in_exclude = false; - for (auto str : params) { + for (const auto& str : params) { if (in_exclude) exclude_strs = str; else if (str == "--notags") @@ -728,60 +716,25 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, s CommandDepthCounter counter; if (!counter.ok()) { - con.printerr("Cannot invoke \"%s\": maximum command depth exceeded (%i)\n", - first.c_str(), CommandDepthCounter::MAX_DEPTH); + con.printerr("Cannot invoke \"{}\": maximum command depth exceeded ({})\n", + first, CommandDepthCounter::MAX_DEPTH); return CR_FAILURE; } if (first.empty()) return CR_NOT_IMPLEMENTED; - if (first.find('\\') != std::string::npos) + if (has_backslashes(first)) { - con.printerr("Replacing backslashes with forward slashes in \"%s\"\n", first.c_str()); - for (size_t i = 0; i < first.size(); i++) - { - if (first[i] == '\\') - first[i] = '/'; - } + con.printerr("Replacing backslashes with forward slashes in \"{}\"\n", first); + replace_backslashes_with_forwardslashes(first); } // let's see what we actually got command_result res; if (first == "help" || first == "man" || first == "?") { - if(!parts.size()) - { - if (con.is_console()) - { - con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n" - "Some basic editing capabilities are included (single-line text editing).\n" - "The console also has a command history - you can navigate it with Up and Down keys.\n" - "On Windows, you may have to resize your console window. The appropriate menu is accessible\n" - "by clicking on the program icon in the top bar of the window.\n\n"); - } - con.print("Here are some basic commands to get you started:\n" - " help|?|man - This text.\n" - " help - Usage help for the given plugin, command, or script.\n" - " tags - List the tags that the DFHack tools are grouped by.\n" - " ls|dir [] - List commands, optionally filtered by a tag or substring.\n" - " Optional parameters:\n" - " --notags: skip printing tags for each command.\n" - " --dev: include commands intended for developers and modders.\n" - " cls|clear - Clear the console.\n" - " fpause - Force DF to pause.\n" - " die - Force DF to close immediately, without saving.\n" - " keybinding - Modify bindings of commands to in-game key shortcuts.\n" - "\n" - "See more commands by running 'ls'.\n\n" - ); - - con.print("DFHack version %s\n", dfhack_version_desc().c_str()); - } - else - { - help_helper(con, parts[0]); - } + return Commands::help(con, *this, first, parts); } else if (first == "tags") { @@ -789,123 +742,11 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, s } else if (first == "load" || first == "unload" || first == "reload") { - bool all = false; - bool load = (first == "load"); - bool unload = (first == "unload"); - bool reload = (first == "reload"); - if (parts.size()) - { - for (auto p = parts.begin(); p != parts.end(); p++) - { - if (p->size() && (*p)[0] == '-') - { - if (p->find('a') != std::string::npos) - all = true; - } - } - auto ret = CR_OK; - if (all) - { - if (load && !plug_mgr->loadAll()) - ret = CR_FAILURE; - else if (unload && !plug_mgr->unloadAll()) - ret = CR_FAILURE; - else if (reload && !plug_mgr->reloadAll()) - ret = CR_FAILURE; - } - else - { - for (auto p = parts.begin(); p != parts.end(); p++) - { - if (!p->size() || (*p)[0] == '-') - continue; - if (load && !plug_mgr->load(*p)) - ret = CR_FAILURE; - else if (unload && !plug_mgr->unload(*p)) - ret = CR_FAILURE; - else if (reload && !plug_mgr->reload(*p)) - ret = CR_FAILURE; - } - } - if (ret != CR_OK) - con.printerr("%s failed\n", first.c_str()); - return ret; - } - else { - con.printerr("%s: no arguments\n", first.c_str()); - return CR_FAILURE; - } + return Commands::load(con, *this, first, parts); } else if( first == "enable" || first == "disable" ) { - CoreSuspender suspend; - bool enable = (first == "enable"); - - if(parts.size()) - { - for (size_t i = 0; i < parts.size(); i++) - { - std::string part = parts[i]; - if (part.find('\\') != std::string::npos) - { - con.printerr("Replacing backslashes with forward slashes in \"%s\"\n", part.c_str()); - for (size_t j = 0; j < part.size(); j++) - { - if (part[j] == '\\') - part[j] = '/'; - } - } - - part = GetAliasCommand(part, true); - - Plugin * plug = (*plug_mgr)[part]; - - if(!plug) - { - std::filesystem::path lua = findScript(part + ".lua"); - if (!lua.empty()) - { - res = enableLuaScript(con, part, enable); - } - else - { - res = CR_NOT_FOUND; - con.printerr("No such plugin or Lua script: %s\n", part.c_str()); - } - } - else if (!plug->can_set_enabled()) - { - res = CR_NOT_IMPLEMENTED; - con.printerr("Cannot %s plugin: %s\n", first.c_str(), part.c_str()); - } - else - { - res = plug->set_enabled(con, enable); - - if (res != CR_OK || plug->is_enabled() != enable) - con.printerr("Could not %s plugin: %s\n", first.c_str(), part.c_str()); - } - } - - return res; - } - else - { - for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) - { - Plugin * plug = it->second; - if (!plug->can_be_enabled()) continue; - - con.print( - "%21s %-3s%s\n", - (plug->getName()+":").c_str(), - plug->is_enabled() ? "on" : "off", - plug->can_set_enabled() ? "" : " (controlled internally)" - ); - } - - Lua::CallLuaModuleFunction(con, "script-manager", "list"); - } + return Commands::enable(con, *this, first, parts); } else if (first == "ls" || first == "dir") { @@ -913,191 +754,27 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, s } else if (first == "plug") { - const char *header_format = "%30s %10s %4s %8s\n"; - const char *row_format = "%30s %10s %4i %8s\n"; - con.print(header_format, "Name", "State", "Cmds", "Enabled"); - - plug_mgr->refresh(); - for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) - { - Plugin * plug = it->second; - if (!plug) - continue; - if (parts.size() && std::find(parts.begin(), parts.end(), plug->getName()) == parts.end()) - continue; - color_value color; - switch (plug->getState()) - { - case Plugin::PS_LOADED: - color = COLOR_RESET; - break; - case Plugin::PS_UNLOADED: - case Plugin::PS_UNLOADING: - color = COLOR_YELLOW; - break; - case Plugin::PS_LOADING: - color = COLOR_LIGHTBLUE; - break; - case Plugin::PS_BROKEN: - color = COLOR_LIGHTRED; - break; - default: - color = COLOR_LIGHTMAGENTA; - break; - } - con.color(color); - con.print(row_format, - plug->getName().c_str(), - Plugin::getStateDescription(plug->getState()), - plug->size(), - (plug->can_be_enabled() - ? (plug->is_enabled() ? "enabled" : "disabled") - : "n/a") - ); - con.color(COLOR_RESET); - } + return Commands::plug(con, *this, first, parts); } else if (first == "type") { - if (!parts.size()) - { - con.printerr("type: no argument\n"); - return CR_WRONG_USAGE; - } - con << parts[0]; - bool builtin = is_builtin(con, parts[0]); - std::filesystem::path lua_path = findScript(parts[0] + ".lua"); - Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); - if (builtin) - { - con << " is a built-in command"; - con << std::endl; - } - else if (IsAlias(parts[0])) - { - con << " is an alias: " << GetAliasCommand(parts[0]) << std::endl; - } - else if (plug) - { - con << " is a command implemented by the plugin " << plug->getName() << std::endl; - } - else if (!lua_path.empty()) - { - con << " is a Lua script: " << lua_path << std::endl; - } - else - { - con << " is not a recognized command." << std::endl; - plug = plug_mgr->getPluginByName(parts[0]); - if (plug) - con << "Plugin " << parts[0] << " exists and implements " << plug->size() << " commands." << std::endl; - return CR_FAILURE; - } + return Commands::type(con, *this, first, parts); } else if (first == "keybinding") { - if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) - { - std::string keystr = parts[1]; - if (parts[0] == "set") - ClearKeyBindings(keystr); - for (int i = parts.size()-1; i >= 2; i--) - { - if (!AddKeyBinding(keystr, parts[i])) { - con.printerr("Invalid key spec: %s\n", keystr.c_str()); - break; - } - } - } - else if (parts.size() >= 2 && parts[0] == "clear") - { - for (size_t i = 1; i < parts.size(); i++) - { - if (!ClearKeyBindings(parts[i])) { - con.printerr("Invalid key spec: %s\n", parts[i].c_str()); - break; - } - } - } - else if (parts.size() == 2 && parts[0] == "list") - { - std::vector list = ListKeyBindings(parts[1]); - if (list.empty()) - con << "No bindings." << std::endl; - for (size_t i = 0; i < list.size(); i++) - con << " " << list[i] << std::endl; - } - else - { - con << "Usage:" << std::endl - << " keybinding list " << std::endl - << " keybinding clear [@context]..." << std::endl - << " keybinding set [@context] \"cmdline\" \"cmdline\"..." << std::endl - << " keybinding add [@context] \"cmdline\" \"cmdline\"..." << std::endl - << "Later adds, and earlier items within one command have priority." << std::endl - << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, `, or Enter)." << std::endl - << "Context may be used to limit the scope of the binding, by" << std::endl - << "requiring the current context to have a certain prefix." << std::endl - << "Current UI context is: " << std::endl - << join_strings("\n", Gui::getCurFocus(true)) << std::endl; - } + return Commands::keybinding(con, *this, first, parts); } else if (first == "alias") { - if (parts.size() >= 3 && (parts[0] == "add" || parts[0] == "replace")) - { - const std::string &name = parts[1]; - std::vector cmd(parts.begin() + 2, parts.end()); - if (!AddAlias(name, cmd, parts[0] == "replace")) - { - con.printerr("Could not add alias %s - already exists\n", name.c_str()); - return CR_FAILURE; - } - } - else if (parts.size() >= 2 && (parts[0] == "delete" || parts[0] == "clear")) - { - if (!RemoveAlias(parts[1])) - { - con.printerr("Could not remove alias %s\n", parts[1].c_str()); - return CR_FAILURE; - } - } - else if (parts.size() >= 1 && (parts[0] == "list")) - { - auto aliases = ListAliases(); - for (auto p : aliases) - { - con << p.first << ": " << join_strings(" ", p.second) << std::endl; - } - } - else - { - con << "Usage: " << std::endl - << " alias add|replace " << std::endl - << " alias delete|clear " << std::endl - << " alias list" << std::endl; - } + return Commands::alias(con, *this, first, parts); } else if (first == "fpause") { - World::SetPauseState(true); -/* TODO: understand how this changes for v50 - if (auto scr = Gui::getViewscreenByType()) - { - scr->worldgen_paused = true; - } -*/ - con.print("The game was forced to pause!\n"); + return Commands::fpause(con, *this, first, parts); } else if (first == "cls" || first == "clear") { - if (con.is_console()) - ((Console&)con).clear(); - else - { - con.printerr("No console to clear.\n"); - return CR_NEEDS_CONSOLE; - } + return Commands::clear(con, *this, first, parts); } else if (first == "die") { @@ -1109,173 +786,27 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, s } else if (first == "kill-lua") { - bool force = false; - for (auto it = parts.begin(); it != parts.end(); ++it) - { - if (*it == "force") - force = true; - } - if (!Lua::Interrupt(force)) - { - con.printerr( - "Failed to register hook. This can happen if you have" - " lua profiling or coverage monitoring enabled. Use" - " 'kill-lua force' to force, but this may disable" - " profiling and coverage monitoring.\n"); - } + return Commands::kill_lua(con, *this, first, parts); } else if (first == "script") { - if(parts.size() == 1) - { - loadScriptFile(con, std::filesystem::weakly_canonical(std::filesystem::path{parts[0]}), false); - } - else - { - con << "Usage:" << std::endl - << " script " << std::endl; - return CR_WRONG_USAGE; - } + return Commands::script(con, *this, first, parts); } else if (first == "hide") { - if (!getConsole().hide()) - { - con.printerr("Could not hide console\n"); - return CR_FAILURE; - } - return CR_OK; + return Commands::hide(con, *this, first, parts); } else if (first == "show") { - if (!getConsole().show()) - { - con.printerr("Could not show console\n"); - return CR_FAILURE; - } - return CR_OK; + return Commands::show(con, *this, first, parts); } else if (first == "sc-script") { - if (parts.empty() || parts[0] == "help" || parts[0] == "?") - { - con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << std::endl; - con << "Valid event names (SC_ prefix is optional):" << std::endl; - for (int i = SC_WORLD_LOADED; i <= SC_UNPAUSED; i++) - { - std::string name = sc_event_name((state_change_event)i); - if (name != "SC_UNKNOWN") - con << " " << name << std::endl; - } - return CR_OK; - } - else if (parts[0] == "list") - { - if(parts.size() < 2) - parts.push_back(""); - if (parts[1].size() && sc_event_id(parts[1]) == SC_UNKNOWN) - { - con << "Unrecognized event name: " << parts[1] << std::endl; - return CR_WRONG_USAGE; - } - for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) - { - if (!parts[1].size() || (it->event == sc_event_id(parts[1]))) - { - con.print("%s (%s): %s%s\n", sc_event_name(it->event).c_str(), - it->save_specific ? "save-specific" : "global", - it->save_specific ? "/raw/" : "/", - it->path.c_str()); - } - } - return CR_OK; - } - else if (parts[0] == "add") - { - if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) - { - con << "Usage: sc-script add EVENT path-to-script [-save]" << std::endl; - return CR_WRONG_USAGE; - } - state_change_event evt = sc_event_id(parts[1]); - if (evt == SC_UNKNOWN) - { - con << "Unrecognized event: " << parts[1] << std::endl; - return CR_FAILURE; - } - bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); - StateChangeScript script(evt, parts[2], save_specific); - for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) - { - if (script == *it) - { - con << "Script already registered" << std::endl; - return CR_FAILURE; - } - } - state_change_scripts.push_back(script); - return CR_OK; - } - else if (parts[0] == "remove") - { - if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) - { - con << "Usage: sc-script remove EVENT path-to-script [-save]" << std::endl; - return CR_WRONG_USAGE; - } - state_change_event evt = sc_event_id(parts[1]); - if (evt == SC_UNKNOWN) - { - con << "Unrecognized event: " << parts[1] << std::endl; - return CR_FAILURE; - } - bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); - StateChangeScript tmp(evt, parts[2], save_specific); - auto it = std::find(state_change_scripts.begin(), state_change_scripts.end(), tmp); - if (it != state_change_scripts.end()) - { - state_change_scripts.erase(it); - return CR_OK; - } - else - { - con << "Unrecognized script" << std::endl; - return CR_FAILURE; - } - } - else - { - con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << std::endl; - return CR_WRONG_USAGE; - } + return Commands::sc_script(con, *this, first, parts); } else if (first == "devel/dump-rpc") { - if (parts.size() == 1) - { - std::ofstream file(parts[0]); - CoreService core; - core.dumpMethods(file); - - for (auto & it : *plug_mgr) - { - Plugin * plug = it.second; - if (!plug) - continue; - - std::unique_ptr svc(plug->rpc_connect(con)); - if (!svc) - continue; - - file << "// Plugin: " << plug->getName() << std::endl; - svc->dumpMethods(file); - } - } - else - { - con << "Usage: devel/dump-rpc \"filename\"" << std::endl; - return CR_WRONG_USAGE; - } + return Commands::dump_rpc(con, *this, first, parts); } else if (RunAlias(con, first, parts, res)) { @@ -1286,7 +817,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, s res = plug_mgr->InvokeCommand(con, first, parts); if (res == CR_WRONG_USAGE) { - help_helper(con, first); + DFHack::help_helper(con, first); } else if (res == CR_NOT_IMPLEMENTED) { @@ -1298,26 +829,26 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, s else if (!no_autocomplete && try_autocomplete(con, first, completed)) res = CR_NOT_IMPLEMENTED; else - con.printerr("%s is not a recognized command.\n", first.c_str()); + con.printerr("{} is not a recognized command.\n", first); if (res == CR_NOT_IMPLEMENTED) { Plugin *p = plug_mgr->getPluginByName(first); if (p) { - con.printerr("%s is a plugin ", first.c_str()); + con.printerr("{} is a plugin ", first); if (p->getState() == Plugin::PS_UNLOADED) - con.printerr("that is not loaded - try \"load %s\" or check stderr.log\n", - first.c_str()); + con.printerr("that is not loaded - try \"load {}\" or check stderr.log\n", + first); else if (p->size()) - con.printerr("that implements %zi commands - see \"help %s\" for details\n", - p->size(), first.c_str()); + con.printerr("that implements {} commands - see \"help {}\" for details\n", + p->size(), first); else con.printerr("but does not implement any commands\n"); } } } else if (res == CR_NEEDS_CONSOLE) - con.printerr("%s needs an interactive console to work.\n" + con.printerr("{} needs an interactive console to work.\n" "Please run this command from the DFHack console.\n\n" #ifdef WIN32 "You can show the console with the 'show' command." @@ -1325,7 +856,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, s "The console is accessible when you run DF from the commandline\n" "via the './dfhack' script." #endif - "\n", first.c_str()); + "\n", first); return res; } @@ -1338,11 +869,26 @@ bool Core::loadScriptFile(color_ostream &out, std::filesystem::path fname, bool INFO(script,out) << "Running script: " << fname << std::endl; std::cerr << "Running script: " << fname << std::endl; } - std::ifstream script{ fname.c_str() }; + + auto pathlist = {getHackPath(), getHackPath().parent_path(), std::filesystem::current_path()}; + + std::filesystem::path path; + + for (auto& p : pathlist) + { + auto candidate = fname.is_relative() ? p / fname : fname; + if (std::filesystem::exists(candidate)) + { + path = candidate; + break; + } + } + + std::ifstream script{ path }; if ( !script ) { if(!silent) - out.printerr("Error loading script: %s\n", fname.string().c_str()); + out.printerr("Error loading script: {}\n", fname); return false; } std::string command; @@ -1381,12 +927,11 @@ static void run_dfhack_init(color_ostream &out, Core *core) } // load baseline defaults - core->loadScriptFile(out, getConfigPath() / "init" / "default.dfhack.init", false); + core->loadScriptFile(out, core->getConfigPath() / "init" / "default.dfhack.init", false); // load user overrides std::vector prefixes(1, "dfhack"); - loadScriptFiles(core, out, prefixes, getConfigPath() / "init"); - + loadScriptFiles(core, out, prefixes, core->getConfigPath() / "init"); // show the terminal if requested auto L = DFHack::Core::getInstance().getLuaState(); Lua::CallLuaModuleFunction(out, L, "dfhack", "getHideConsoleOnStartup", 0, 1, @@ -1408,16 +953,16 @@ static void fInitthread(IODATA * iod) // A thread function... for the interactive console. static void fIOthread(IODATA * iod) { - static const std::filesystem::path HISTORY_FILE = getConfigPath() / "dfhack.history"; - Core * core = iod->core; + std::filesystem::path HISTORY_FILE = core->getConfigPath() / "dfhack.history"; + PluginManager * plug_mgr = iod->plug_mgr; CommandHistory main_history; main_history.load(HISTORY_FILE.c_str()); Console & con = core->getConsole(); - if (plug_mgr == 0) + if (plug_mgr == nullptr) { con.printerr("Something horrible happened in Core's constructor...\n"); return; @@ -1426,9 +971,9 @@ static void fIOthread(IODATA * iod) run_dfhack_init(con, core); con.print("DFHack is ready. Have a nice day!\n" - "DFHack version %s\n" + "DFHack version {}\n" "Type in '?' or 'help' for general help, 'ls' to see all commands.\n", - dfhack_version_desc().c_str()); + dfhack_version_desc()); int clueless_counter = 0; @@ -1437,7 +982,7 @@ static void fIOthread(IODATA * iod) while (true) { - std::string command = ""; + std::string command; int ret; while ((ret = con.lineedit("[DFHack]# ",command, main_history)) == Console::RETRY); @@ -1479,8 +1024,7 @@ Core::~Core() Core::Core() : d(std::make_unique()), script_path_mutex{}, - HotkeyMutex{}, - HotkeyCond{}, + armok_mutex{}, alias_mutex{}, started{false}, CoreSuspendMutex{}, @@ -1489,18 +1033,16 @@ Core::Core() : toolCount{0} { // init the console. This must be always the first step! - plug_mgr = 0; + plug_mgr = nullptr; errorstate = false; vinfo = 0; - memset(&(s_mods), 0, sizeof(s_mods)); // set up hotkey capture suppress_duplicate_keyboard_events = true; - hotkey_set = NO; - last_world_data_ptr = NULL; - last_local_map_ptr = NULL; + last_world_data_ptr = nullptr; + last_local_map_ptr = nullptr; last_pause_state = false; - top_viewscreen = NULL; + top_viewscreen = nullptr; color_ostream::log_errors_to_stderr = true; }; @@ -1515,16 +1057,16 @@ void Core::fatal (std::string output, const char * title) out << "DFHack will now deactivate.\n"; if(con.isInited()) { - con.printerr("%s", out.str().c_str()); + con.printerr("{}", out.str()); con.reset_color(); con.print("\n"); } - fprintf(stderr, "%s\n", out.str().c_str()); + fmt::print(stderr, "{}\n", out.str()); out << "Check file stderr.log for details.\n"; std::cout << "DFHack fatal error: " << out.str() << std::endl; if (!title) title = "DFHack error!"; - DFSDL::DFSDL_ShowSimpleMessageBox(0x10 /* SDL_MESSAGEBOX_ERROR */, title, out.str().c_str(), NULL); + DFSDL::DFSDL_ShowSimpleMessageBox(0x10 /* SDL_MESSAGEBOX_ERROR */, title, out.str().c_str(), nullptr); bool is_headless = bool(getenv("DFHACK_HEADLESS")); if (is_headless) @@ -1535,19 +1077,30 @@ void Core::fatal (std::string output, const char * title) std::filesystem::path Core::getHackPath() { - return p->getPath() / "hack"; + return hack_path; } df::viewscreen * Core::getTopViewscreen() { return getInstance().top_viewscreen; } -bool Core::InitMainThread() { +bool Core::InitMainThread(std::filesystem::path path) { + assert(active_instance == nullptr); + + // set this instance as the active instance + active_instance = this; + // this hook is always called from DF's main (render) thread, so capture this thread id df_render_thread = std::this_thread::get_id(); + hack_path = path; Filesystem::init(); + #ifdef LINUX_BUILD + extern void dfhack_crashlog_init(); + dfhack_crashlog_init(); + #endif + // Re-route stdout and stderr again - DF seems to set up stdout and // stderr.txt on Windows as of 0.43.05. Also, log before switching files to // make it obvious what's going on if someone checks the *.txt files. @@ -1567,6 +1120,7 @@ bool Core::InitMainThread() { std::cerr << "Build url: " << Version::dfhack_run_url() << std::endl; } std::cerr << "Starting with working directory: " << Filesystem::getcwd() << std::endl; + std::cerr << "Hack path: " << getHackPath() << std::endl; std::cerr << "Binding to SDL.\n"; if (!DFSDL::init(con)) { @@ -1575,16 +1129,12 @@ bool Core::InitMainThread() { } // find out what we are... - #ifdef LINUX_BUILD - const char * path = "hack/symbols.xml"; - #else - const char * path = "hack\\symbols.xml"; - #endif + std::filesystem::path symbols_path = getHackPath() / "symbols.xml"; auto local_vif = std::make_unique(); std::cerr << "Identifying DF version.\n"; try { - local_vif->loadFile(path); + local_vif->loadFile(symbols_path); } catch(Error::All & err) { @@ -1604,7 +1154,7 @@ bool Core::InitMainThread() { { if (!Version::git_xml_match()) { - const char *msg = ( + constexpr auto msg = ( "*******************************************************\n" "* BIG, UGLY ERROR MESSAGE *\n" "*******************************************************\n" @@ -1675,6 +1225,7 @@ bool Core::InitMainThread() { { "world", sizeof(df::world) }, { "game", sizeof(df::gamest) }, { "plotinfo", sizeof(df::plotinfost) }, + { "gps", sizeof(df::graphic) }, }; for (auto& gte : *df::global::global_table) @@ -1711,9 +1262,9 @@ bool Core::InitSimulationThread() { // the update hook is only called from the simulation thread, so capture this thread id df_simulation_thread = std::this_thread::get_id(); - if(started) + if (started) return true; - if(errorstate) + if (errorstate) return false; // Lock the CoreSuspendMutex until the thread exits or call Core::Shutdown @@ -1755,60 +1306,64 @@ bool Core::InitSimulationThread() std::cout << "Console disabled.\n"; } } - else if(con.init(false)) + else if (con.init(false)) std::cerr << "Console is running.\n"; else std::cerr << "Console has failed to initialize!\n"; -/* - // dump offsets to a file - std::ofstream dump("offsets.log"); - if(!dump.fail()) - { - //dump << vinfo->PrintOffsets(); - dump.close(); - } - */ - // initialize data defs + /* + // dump offsets to a file + std::ofstream dump("offsets.log"); + if(!dump.fail()) + { + //dump << vinfo->PrintOffsets(); + dump.close(); + } + */ + // initialize data defs virtual_identity::Init(this); // create config directory if it doesn't already exist if (!Filesystem::mkdir_recursive(getConfigPath())) - con.printerr("Failed to create config directory: '%s'\n", getConfigPath().c_str()); + con.printerr("Failed to create config directory: '{}'\n", getConfigPath()); // copy over default config files if necessary std::map config_files; std::map default_config_files; if (Filesystem::listdir_recursive(getConfigPath(), config_files, 10, false) != 0) - con.printerr("Failed to list directory: '%s'\n", getConfigPath().c_str()); + con.printerr("Failed to list directory: '{}'\n", getConfigPath()); else if (Filesystem::listdir_recursive(getConfigDefaultsPath(), default_config_files, 10, false) != 0) - con.printerr("Failed to list directory: '%s'\n", getConfigDefaultsPath().c_str()); + con.printerr("Failed to list directory: '{}'\n", getConfigDefaultsPath()); else { // ensure all config file directories exist before we start copying files - for (auto &entry : default_config_files) { + for (auto& entry : default_config_files) + { // skip over files if (!entry.second) continue; std::filesystem::path dirname = getConfigPath() / entry.first; if (!Filesystem::mkdir_recursive(dirname)) - con.printerr("Failed to create config directory: '%s'\n", dirname.c_str()); + con.printerr("Failed to create config directory: '{}'\n", dirname); } // copy files from the default tree that don't already exist in the config tree - for (auto &entry : default_config_files) { + for (auto& entry : default_config_files) + { // skip over directories if (entry.second) continue; std::filesystem::path filename = entry.first; - if (!config_files.count(filename)) { + if (!config_files.contains(filename)) + { std::filesystem::path src_file = getConfigDefaultsPath() / filename; if (!Filesystem::isfile(src_file)) continue; std::filesystem::path dest_file = getConfigPath() / filename; std::ifstream src(src_file, std::ios::binary); std::ofstream dest(dest_file, std::ios::binary); - if (!src.good() || !dest.good()) { - con.printerr("Copy failed: '%s'\n", filename.c_str()); + if (!src.good() || !dest.good()) + { + con.printerr("Copy failed: '{}'\n", filename); continue; } dest << src.rdbuf(); @@ -1818,6 +1373,17 @@ bool Core::InitSimulationThread() } } + // set lua default path if not already set + if (std::getenv("DFHACK_LUA_PATH") == nullptr) + { + std::filesystem::path lua_path = getHackPath() / "lua" / "?.lua"; +#ifdef WIN32 + _putenv_s("DFHACK_LUA_PATH", lua_path.string().c_str()); +#else + setenv("DFHACK_LUA_PATH", lua_path.string().c_str(), 1); +#endif + } + loadScriptPaths(con); // initialize common lua context @@ -1843,7 +1409,9 @@ bool Core::InitSimulationThread() plug_mgr->init(); std::cerr << "Starting the TCP listener.\n"; auto listen = ServerMain::listen(RemoteClient::GetDefaultPort()); - IODATA *temp = new IODATA; + this->hotkey_mgr = new HotkeyManager(); + + auto *temp = new IODATA; temp->core = this; temp->plug_mgr = plug_mgr; @@ -1860,8 +1428,6 @@ bool Core::InitSimulationThread() } std::cerr << "Starting DF input capture thread.\n"; - // set up hotkey capture - d->hotkeythread = std::thread(fHKthread, temp); started = true; modstate = 0; @@ -1878,13 +1444,13 @@ bool Core::InitSimulationThread() if (raw[offset] == '"') { offset++; - size_t next = raw.find("\"", offset); + size_t next = raw.find('"', offset); args.push_back(raw.substr(offset, next - offset)); offset = next + 2; } else { - size_t next = raw.find(" ", offset); + size_t next = raw.find(' ', offset); if (next == std::string::npos) { args.push_back(raw.substr(offset)); @@ -1903,9 +1469,9 @@ bool Core::InitSimulationThread() if (first.length() > 0 && first[0] == '+') { std::vector cmd; - for (it++; it != args.end(); it++) { - const std::string & arg = *it; - if (arg.length() > 0 && arg[0] == '+') + for (const auto& arg : args) + { + if (!arg.empty() && arg[0] == '+') { break; } @@ -1915,9 +1481,9 @@ bool Core::InitSimulationThread() if (runCommand(con, first.substr(1), cmd) != CR_OK) { std::cerr << "Error running command: " << first.substr(1); - for (auto it2 = cmd.begin(); it2 != cmd.end(); it2++) + for (const auto& arg : cmd) { - std::cerr << " \"" << *it2 << "\""; + std::cerr << " \"" << arg << "\""; } std::cerr << "\n"; } @@ -1935,55 +1501,10 @@ bool Core::InitSimulationThread() return true; } -/// sets the current hotkey command -bool Core::setHotkeyCmd( std::string cmd ) -{ - // access command - std::lock_guard lock(HotkeyMutex); - hotkey_set = SET; - hotkey_cmd = cmd; - HotkeyCond.notify_all(); - return true; -} -/// removes the hotkey command and gives it to the caller thread -std::string Core::getHotkeyCmd( bool &keep_going ) -{ - std::string returner; - std::unique_lock lock(HotkeyMutex); - HotkeyCond.wait(lock, [this]() -> bool {return this->hotkey_set;}); - if (hotkey_set == SHUTDOWN) { - keep_going = false; - return returner; - } - hotkey_set = NO; - returner = hotkey_cmd; - hotkey_cmd.clear(); - return returner; -} - -void Core::print(const char *format, ...) -{ - color_ostream_proxy proxy(getInstance().con); - - va_list args; - va_start(args,format); - proxy.vprint(format,args); - va_end(args); -} - -void Core::printerr(const char *format, ...) -{ - color_ostream_proxy proxy(getInstance().con); - - va_list args; - va_start(args,format); - proxy.vprinterr(format,args); - va_end(args); -} Core& Core::getInstance() { - static Core instance; - return instance; + assert(Core::active_instance != nullptr); + return *Core::active_instance; } bool Core::isSuspended(void) @@ -1996,7 +1517,7 @@ void Core::doUpdate(color_ostream &out) Lua::Core::Reset(out, "DF code execution"); // find the current viewscreen - df::viewscreen *screen = NULL; + df::viewscreen *screen = nullptr; if (df::global::gview) { screen = &df::global::gview->view; @@ -2158,76 +1679,84 @@ void Core::onUpdate(color_ostream &out) perf_counters.incCounter(perf_counters.update_lua_ms, step_start_ms); } -void getFilesWithPrefixAndSuffix(const std::filesystem::path& folder, const std::string& prefix, const std::string& suffix, std::vector& result) { +static void getFilesWithPrefixAndSuffix(const std::filesystem::path& folder, const std::string& prefix, const std::string& suffix, std::vector& result) { std::vector files; DFHack::Filesystem::listdir(folder, files); - for ( auto f : files) { + for ( const auto& f : files) { if (f.stem().string().starts_with(prefix) && f.extension() == suffix) result.push_back(f); } - return; } -size_t loadScriptFiles(Core* core, color_ostream& out, const std::vector& prefix, const std::filesystem::path& folder) { +size_t loadScriptFiles(Core* core, color_ostream& out, const std::span prefix, const std::filesystem::path& folder) { static const std::string suffix = ".init"; std::vector scriptFiles; - for ( size_t a = 0; a < prefix.size(); a++ ) { - getFilesWithPrefixAndSuffix(folder, prefix[a], ".init", scriptFiles); + for ( const auto& p : prefix ) { + getFilesWithPrefixAndSuffix(folder, p, ".init", scriptFiles); } - std::sort(scriptFiles.begin(), scriptFiles.end(), - [](const std::filesystem::path& a, const std::filesystem::path& b) { - return a.stem() < b.stem(); - }); + + std::ranges::sort(scriptFiles, + [](const std::filesystem::path& a, const std::filesystem::path& b) { + return a.stem() < b.stem(); + }); + size_t result = 0; - for ( size_t a = 0; a < scriptFiles.size(); a++ ) { + for ( const auto& file : scriptFiles) { result++; - core->loadScriptFile(out, folder / scriptFiles[a], false); + core->loadScriptFile(out, folder / file, false); } return result; } namespace DFHack { namespace X { - typedef state_change_event Key; - typedef std::vector Val; - typedef std::pair Entry; - typedef std::vector EntryVector; - typedef std::map InitVariationTable; - - EntryVector computeInitVariationTable(void* none, ...) { - va_list list; - va_start(list,none); + using Key = state_change_event; + using Val = std::vector; + using Entry = std::pair; + using EntryVector = std::vector; + using InitVariationTable = std::map; + + template + concept VariationTableTypes = std::same_as || std::convertible_to; + + template + static EntryVector computeInitVariationTable(Ts&&... ts) { + using Arg = std::variant; + std::vector args{ Arg{std::forward(ts)}... }; + + Key current_key = SC_UNKNOWN; EntryVector result; - while(true) { - Key key = (Key)va_arg(list,int); - if ( key == SC_UNKNOWN ) - break; - Val val; - while (true) { - const char *v = va_arg(list, const char *); - if (!v || !v[0]) - break; - val.emplace_back(v); + Val val; + for (auto& arg : args) { + if (std::holds_alternative(arg)) { + if (current_key != SC_UNKNOWN && !val.empty()) { + result.emplace_back(current_key, val); + val.clear(); + } + current_key = std::get(arg); + } else if (std::holds_alternative(arg)) { + auto str = std::get(arg); + if (!str.empty()) + val.emplace_back(str); } - result.push_back(Entry(key,val)); } - va_end(list); + return result; } - InitVariationTable getTable(const EntryVector& vec) { + static InitVariationTable getTable(const EntryVector& vec) { return InitVariationTable(vec.begin(),vec.end()); } } } void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event event) { - static const X::InitVariationTable table = X::getTable(X::computeInitVariationTable(0, - (int)SC_WORLD_LOADED, "onLoad", "onLoadWorld", "onWorldLoaded", "", - (int)SC_WORLD_UNLOADED, "onUnload", "onUnloadWorld", "onWorldUnloaded", "", - (int)SC_MAP_LOADED, "onMapLoad", "onLoadMap", "", - (int)SC_MAP_UNLOADED, "onMapUnload", "onUnloadMap", "", - (int)SC_UNKNOWN + static const X::InitVariationTable table = X::getTable(X::computeInitVariationTable( + SC_WORLD_LOADED, "onLoad", "onLoadWorld", "onWorldLoaded", + SC_WORLD_UNLOADED, "onUnload", "onUnloadWorld", "onWorldUnloaded", + SC_MAP_LOADED, "onMapLoad", "onLoadMap", + SC_MAP_UNLOADED, "onMapUnload", "onUnloadMap", + SC_UNKNOWN )); if (!df::global::world) @@ -2246,17 +1775,17 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve loadScriptFiles(this, out, set, rawFolder); } - for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) + for (const auto& script : state_change_scripts) { - if (it->event == event) + if (script.event == event) { - if (!it->save_specific) + if (!script.save_specific) { - loadScriptFile(out, it->path, false); + loadScriptFile(out, script.path, false); } - else if (it->save_specific && isWorldLoaded()) + else if (script.save_specific && isWorldLoaded()) { - loadScriptFile(out, rawFolder / it->path, false); + loadScriptFile(out, rawFolder / script.path, false); } } } @@ -2266,7 +1795,7 @@ void Core::onStateChange(color_ostream &out, state_change_event event) { using df::global::gametype; static md5wrapper md5w; - static std::string ostype = ""; + static std::string ostype; if (!ostype.size()) { @@ -2317,12 +1846,12 @@ void Core::onStateChange(color_ostream &out, state_change_event event) if (evtlog.fail()) { if (DFHack::Filesystem::isdir(save_dir)) - out.printerr("Could not append to %s\n", evtlogpath.c_str()); + out.printerr("Could not append to {}\n", evtlogpath); } else { char timebuf[30]; - time_t rawtime = time(NULL); + time_t rawtime = time(nullptr); struct tm * timeinfo = localtime(&rawtime); strftime(timebuf, sizeof(timebuf), "[%Y-%m-%dT%H:%M:%S%z] ", timeinfo); evtlog << timebuf; @@ -2382,6 +1911,11 @@ void Core::onStateChange(color_ostream &out, state_change_event event) int Core::Shutdown ( void ) { + #ifdef LINUX_BUILD + extern void dfhack_crashlog_shutdown(); + dfhack_crashlog_shutdown(); + #endif + if(errorstate) return true; errorstate = 1; @@ -2394,29 +1928,31 @@ int Core::Shutdown ( void ) con.shutdown(); } - if (d->hotkeythread.joinable()) { - std::unique_lock hot_lock(HotkeyMutex); - hotkey_set = SHUTDOWN; - HotkeyCond.notify_one(); - } - ServerMain::block(); - d->hotkeythread.join(); d->iothread.join(); + if (hotkey_mgr) { + delete hotkey_mgr; + hotkey_mgr = nullptr; + } + if(plug_mgr) { delete plug_mgr; - plug_mgr = 0; + plug_mgr = nullptr; } + // invalidate all modules - allModules.clear(); Textures::cleanup(); DFSDL::cleanup(); - DFSteam::cleanup(getConsole()); - memset(&(s_mods), 0, sizeof(s_mods)); + DFSteam::cleanup(); + d.reset(); + + // clear active instance + Core::active_instance = nullptr; + return -1; } @@ -2468,27 +2004,35 @@ bool Core::DFH_ncurses_key(int key) return ncurses_wgetch(key, dummy); } -bool Core::getSuppressDuplicateKeyboardEvents() { +bool Core::getSuppressDuplicateKeyboardEvents() const { return suppress_duplicate_keyboard_events; } void Core::setSuppressDuplicateKeyboardEvents(bool suppress) { - DEBUG(keybinding).print("setting suppress_duplicate_keyboard_events to %s\n", - suppress ? "true" : "false"); + DEBUG(keybinding).print("setting suppress_duplicate_keyboard_events to {}\n", + suppress); suppress_duplicate_keyboard_events = suppress; } void Core::setMortalMode(bool value) { - std::lock_guard lock(HotkeyMutex); - mortal_mode = value; + mortal_mode.exchange(value); +} + +bool Core::getMortalMode() { + return mortal_mode.load(); } void Core::setArmokTools(const std::vector &tool_names) { - std::lock_guard lock(HotkeyMutex); + std::lock_guard lock(armok_mutex); armok_tools.clear(); armok_tools.insert(tool_names.begin(), tool_names.end()); } +bool Core::isArmokTool(const std::string& tool) { + std::lock_guard lock(armok_mutex); + return armok_tools.contains(tool); +} + // returns true if the event is handled bool Core::DFH_SDL_Event(SDL_Event* ev) { uint32_t start_ms = p->getTickCount(); @@ -2497,6 +2041,10 @@ bool Core::DFH_SDL_Event(SDL_Event* ev) { return ret; } +void Core::DFH_SDL_Loop() { + DFHack::runRenderThreadCallbacks(); +} + bool Core::doSdlInputEvent(SDL_Event* ev) { // this should only ever be called from the render thread @@ -2525,44 +2073,47 @@ bool Core::doSdlInputEvent(SDL_Event* ev) modstate = (ev->type == SDL_KEYDOWN) ? modstate | DFH_MOD_CTRL : modstate & ~DFH_MOD_CTRL; else if (sym == SDLK_LALT || sym == SDLK_RALT) modstate = (ev->type == SDL_KEYDOWN) ? modstate | DFH_MOD_ALT : modstate & ~DFH_MOD_ALT; + else if (sym == SDLK_LGUI || sym == SDLK_RGUI) // Renamed to LMETA/RMETA in SDL3 + modstate = (ev->type == SDL_KEYDOWN) ? modstate | DFH_MOD_SUPER : modstate & ~DFH_MOD_SUPER; else if (ke.state == SDL_PRESSED && !hotkey_states[sym]) { // the check against hotkey_states[sym] ensures we only process keybindings once per keypress - DEBUG(keybinding).print("key down: sym=%d (%c)\n", sym, sym); - if (SelectHotkey(sym, modstate)) { + DEBUG(keybinding).print("key down: sym={}, char={}\n", sym, static_cast(sym)); + if ((hotkey_mgr->handleKeybind(sym, modstate))) { hotkey_states[sym] = true; if (modstate & (DFH_MOD_CTRL | DFH_MOD_ALT)) { DEBUG(keybinding).print("modifier key detected; not inhibiting SDL key down event\n"); return false; } - DEBUG(keybinding).print("%sinhibiting SDL key down event\n", + DEBUG(keybinding).print("{}inhibiting SDL key down event\n", suppress_duplicate_keyboard_events ? "" : "not "); return suppress_duplicate_keyboard_events; } } else if (ke.state == SDL_RELEASED) { - DEBUG(keybinding).print("key up: sym=%d (%c)\n", sym, sym); + DEBUG(keybinding).print("key up: sym={}, char={}\n", sym, static_cast(sym)); hotkey_states[sym] = false; } } else if (ev->type == SDL_MOUSEBUTTONDOWN) { auto &but = ev->button; - DEBUG(keybinding).print("mouse button down: button=%d\n", but.button); + DEBUG(keybinding).print("mouse button down: button={}\n", but.button); // don't mess with the first three buttons, which are critical elements of DF's control scheme if (but.button > 3) { - SDL_Keycode sym = SDLK_F13 + but.button - 4; - if (sym <= SDLK_F24 && SelectHotkey(sym, modstate)) + // We represent mouse buttons as a negative number, permitting buttons 4-15 + SDL_Keycode sym = -but.button; + if (sym >= -15 && sym <= -4 && hotkey_mgr->handleKeybind(sym, modstate)) return suppress_duplicate_keyboard_events; } } else if (ev->type == SDL_TEXTINPUT) { auto &te = ev->text; - DEBUG(keybinding).print("text input: '%s' (modifiers: %s%s%s)\n", + DEBUG(keybinding).print("text input: '{}' (modifiers: {}{}{})\n", te.text, modstate & DFH_MOD_SHIFT ? "Shift" : "", modstate & DFH_MOD_CTRL ? "Ctrl" : "", modstate & DFH_MOD_ALT ? "Alt" : ""); if (strlen(te.text) == 1 && hotkey_states[te.text[0]]) { - DEBUG(keybinding).print("%sinhibiting SDL text event\n", + DEBUG(keybinding).print("{}inhibiting SDL text event\n", suppress_duplicate_keyboard_events ? "" : "not "); return suppress_duplicate_keyboard_events; } @@ -2571,236 +2122,6 @@ bool Core::doSdlInputEvent(SDL_Event* ev) return false; } -bool Core::SelectHotkey(int sym, int modifiers) -{ - // Find the topmost viewscreen - if (!df::global::gview || !df::global::plotinfo) - return false; - - df::viewscreen *screen = &df::global::gview->view; - while (screen->child) - screen = screen->child; - - if (sym == SDLK_KP_ENTER) - sym = SDLK_RETURN; - - std::string cmd; - - DEBUG(keybinding).print("checking hotkeys for sym=%d (%c), modifiers=%x\n", sym, sym, modifiers); - - { - std::lock_guard lock(HotkeyMutex); - - // Check the internal keybindings - std::vector &bindings = key_bindings[sym]; - for (int i = bindings.size()-1; i >= 0; --i) { - auto &binding = bindings[i]; - DEBUG(keybinding).print("examining hotkey with commandline: '%s'\n", binding.cmdline.c_str()); - - if (binding.modifiers != modifiers) { - DEBUG(keybinding).print("skipping keybinding due to modifiers mismatch: 0x%x != 0x%x\n", - binding.modifiers, modifiers); - continue; - } - if (!binding.focus.empty()) { - if (!Gui::matchFocusString(binding.focus)) { - std::vector focusStrings = Gui::getCurFocus(true); - DEBUG(keybinding).print("skipping keybinding due to focus string mismatch: '%s' != '%s'\n", - join_strings(", ", focusStrings).c_str(), binding.focus.c_str()); - continue; - } - } - if (!plug_mgr->CanInvokeHotkey(binding.command[0], screen)) { - DEBUG(keybinding).print("skipping keybinding due to hotkey guard rejection (command: '%s')\n", - binding.command[0].c_str()); - continue; - } - if (mortal_mode && armok_tools.contains(binding.command[0])) { - DEBUG(keybinding).print("skipping keybinding due to mortal mode (command: '%s')\n", - binding.command[0].c_str()); - continue; - } - - cmd = binding.cmdline; - DEBUG(keybinding).print("matched hotkey\n"); - break; - } - - if (cmd.empty()) { - // Check the hotkey keybindings - int idx = sym - SDLK_F1; - if(idx >= 0 && idx < 8) - { -/* TODO: understand how this changes for v50 - if (modifiers & 1) - idx += 8; - - if (strict_virtual_cast(screen) && - df::global::plotinfo->main.mode != ui_sidebar_mode::Hotkeys && - df::global::plotinfo->main.hotkeys[idx].cmd == df::ui_hotkey::T_cmd::None) - { - cmd = df::global::plotinfo->main.hotkeys[idx].name; - } -*/ - } - } - } - - if (!cmd.empty()) { - setHotkeyCmd(cmd); - return true; - } - else - return false; -} - -static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string *pfocus) -{ - *pmod = 0; - - if (pfocus) - { - *pfocus = ""; - - size_t idx = keyspec.find('@'); - if (idx != std::string::npos) - { - *pfocus = keyspec.substr(idx+1); - keyspec = keyspec.substr(0, idx); - } - } - - // ugh, ugly - for (;;) { - if (keyspec.size() > 6 && keyspec.substr(0, 6) == "Shift-") { - *pmod |= 1; - keyspec = keyspec.substr(6); - } else if (keyspec.size() > 5 && keyspec.substr(0, 5) == "Ctrl-") { - *pmod |= 2; - keyspec = keyspec.substr(5); - } else if (keyspec.size() > 4 && keyspec.substr(0, 4) == "Alt-") { - *pmod |= 4; - keyspec = keyspec.substr(4); - } else - break; - } - - if (keyspec.size() == 1 && keyspec[0] >= 'A' && keyspec[0] <= 'Z') { - *psym = SDLK_a + (keyspec[0]-'A'); - return true; - } else if (keyspec.size() == 1 && keyspec[0] == '`') { - *psym = SDLK_BACKQUOTE; - return true; - } else if (keyspec.size() == 1 && keyspec[0] >= '0' && keyspec[0] <= '9') { - *psym = SDLK_0 + (keyspec[0]-'0'); - return true; - } else if (keyspec.size() == 2 && keyspec[0] == 'F' && keyspec[1] >= '1' && keyspec[1] <= '9') { - *psym = SDLK_F1 + (keyspec[1]-'1'); - return true; - } else if (keyspec.size() == 3 && keyspec.substr(0, 2) == "F1" && keyspec[2] >= '0' && keyspec[2] <= '2') { - *psym = SDLK_F10 + (keyspec[2]-'0'); - return true; - } else if (keyspec.size() == 6 && keyspec.substr(0, 5) == "MOUSE" && keyspec[5] >= '4' && keyspec[5] <= '9') { - *psym = SDLK_F13 + (keyspec[5]-'4'); - return true; - } else if (keyspec.size() == 7 && keyspec.substr(0, 6) == "MOUSE1" && keyspec[5] >= '0' && keyspec[5] <= '5') { - *psym = SDLK_F19 + (keyspec[5]-'0'); - return true; - } else if (keyspec == "Enter") { - *psym = SDLK_RETURN; - return true; - } else - return false; -} - -bool Core::ClearKeyBindings(std::string keyspec) -{ - int sym, mod; - std::string focus; - if (!parseKeySpec(keyspec, &sym, &mod, &focus)) - return false; - - std::lock_guard lock(HotkeyMutex); - - std::vector &bindings = key_bindings[sym]; - for (int i = bindings.size()-1; i >= 0; --i) { - if (bindings[i].modifiers == mod && prefix_matches(focus, bindings[i].focus)) - bindings.erase(bindings.begin()+i); - } - - return true; -} - -bool Core::AddKeyBinding(std::string keyspec, std::string cmdline) -{ - size_t at_pos = keyspec.find('@'); - if (at_pos != std::string::npos) - { - std::string raw_spec = keyspec.substr(0, at_pos); - std::string raw_focus = keyspec.substr(at_pos + 1); - if (raw_focus.find('|') != std::string::npos) - { - std::vector focus_strings; - split_string(&focus_strings, raw_focus, "|"); - for (size_t i = 0; i < focus_strings.size(); i++) - { - if (!AddKeyBinding(raw_spec + "@" + focus_strings[i], cmdline)) - return false; - } - return true; - } - } - int sym; - KeyBinding binding; - if (!parseKeySpec(keyspec, &sym, &binding.modifiers, &binding.focus)) - return false; - - cheap_tokenise(cmdline, binding.command); - if (binding.command.empty()) - return false; - - std::lock_guard lock(HotkeyMutex); - - // Don't add duplicates - std::vector &bindings = key_bindings[sym]; - for (int i = bindings.size()-1; i >= 0; --i) { - if (bindings[i].modifiers == binding.modifiers && - bindings[i].cmdline == cmdline && - bindings[i].focus == binding.focus) - return true; - } - - binding.cmdline = cmdline; - bindings.push_back(binding); - return true; -} - -std::vector Core::ListKeyBindings(std::string keyspec) -{ - int sym, mod; - std::vector rv; - std::string focus; - if (!parseKeySpec(keyspec, &sym, &mod, &focus)) - return rv; - - std::lock_guard lock(HotkeyMutex); - - std::vector &bindings = key_bindings[sym]; - for (int i = bindings.size()-1; i >= 0; --i) { - if (focus.size() && focus != bindings[i].focus) - continue; - if (bindings[i].modifiers == mod) - { - std::string cmd = bindings[i].cmdline; - if (!bindings[i].focus.empty()) - cmd = "@" + bindings[i].focus + ": " + cmd; - rv.push_back(cmd); - } - } - - return rv; -} - bool Core::AddAlias(const std::string &name, const std::vector &command, bool replace) { std::lock_guard lock(alias_mutex); @@ -2826,7 +2147,7 @@ bool Core::RemoveAlias(const std::string &name) bool Core::IsAlias(const std::string &name) { std::lock_guard lock(alias_mutex); - return aliases.find(name) != aliases.end(); + return aliases.contains(name); } bool Core::RunAlias(color_ostream &out, const std::string &name, @@ -2860,145 +2181,3 @@ std::string Core::GetAliasCommand(const std::string &name, bool ignore_params) return aliases[name][0]; return join_strings(" ", aliases[name]); } - -///////////////// -// ClassNameCheck -///////////////// - -// Since there is no Process.cpp, put ClassNameCheck stuff in Core.cpp - -static std::set known_class_names; -static std::map known_vptrs; - -ClassNameCheck::ClassNameCheck(std::string _name) : name(_name), vptr(0) -{ - known_class_names.insert(name); -} - -ClassNameCheck &ClassNameCheck::operator= (const ClassNameCheck &b) -{ - name = b.name; vptr = b.vptr; return *this; -} - -bool ClassNameCheck::operator() (Process *p, void * ptr) const { - if (vptr == 0 && p->readClassName(ptr) == name) - { - vptr = ptr; - known_vptrs[name] = ptr; - } - return (vptr && vptr == ptr); -} - -void ClassNameCheck::getKnownClassNames(std::vector &names) -{ - std::set::iterator it = known_class_names.begin(); - - for (; it != known_class_names.end(); it++) - names.push_back(*it); -} - -MemoryPatcher::MemoryPatcher(Process *p_) : p(p_) -{ - if (!p) - p = Core::getInstance().p.get(); -} - -MemoryPatcher::~MemoryPatcher() -{ - close(); -} - -bool MemoryPatcher::verifyAccess(void *target, size_t count, bool write) -{ - uint8_t *sptr = (uint8_t*)target; - uint8_t *eptr = sptr + count; - - // Find the valid memory ranges - if (ranges.empty()) - p->getMemRanges(ranges); - - // Find the ranges that this area spans - unsigned start = 0; - while (start < ranges.size() && ranges[start].end <= sptr) - start++; - if (start >= ranges.size() || ranges[start].start > sptr) - return false; - - unsigned end = start+1; - while (end < ranges.size() && ranges[end].start < eptr) - { - if (ranges[end].start != ranges[end-1].end) - return false; - end++; - } - if (ranges[end-1].end < eptr) - return false; - - // Verify current permissions - for (unsigned i = start; i < end; i++) - if (!ranges[i].valid || !(ranges[i].read || ranges[i].execute) || ranges[i].shared) - return false; - - // Apply writable permissions & update - for (unsigned i = start; i < end; i++) - { - auto &perms = ranges[i]; - if ((perms.write || !write) && perms.read) - continue; - - save.push_back(perms); - perms.write = perms.read = true; - if (!p->setPermissions(perms, perms)) - return false; - } - - return true; -} - -bool MemoryPatcher::write(void *target, const void *src, size_t size) -{ - if (!makeWritable(target, size)) - return false; - - memmove(target, src, size); - - p->flushCache(target, size); - return true; -} - -void MemoryPatcher::close() -{ - for (size_t i = 0; i < save.size(); i++) - p->setPermissions(save[i], save[i]); - - save.clear(); - ranges.clear(); -}; - - -bool Process::patchMemory(void *target, const void* src, size_t count) -{ - MemoryPatcher patcher(this); - - return patcher.write(target, src, count); -} - -/******************************************************************************* - M O D U L E S -*******************************************************************************/ - -#define MODULE_GETTER(TYPE) \ -TYPE * Core::get##TYPE() \ -{ \ - if(errorstate) return NULL;\ - if(!s_mods.p##TYPE)\ - {\ - std::unique_ptr mod = create##TYPE();\ - s_mods.p##TYPE = (TYPE *) mod.get();\ - allModules.push_back(std::move(mod));\ - }\ - return s_mods.p##TYPE;\ -} - -MODULE_GETTER(Materials); -MODULE_GETTER(Graphic); diff --git a/library/Crashlog.cpp b/library/Crashlog.cpp new file mode 100644 index 0000000000..3860e0106e --- /dev/null +++ b/library/Crashlog.cpp @@ -0,0 +1,190 @@ +#include "DFHackVersion.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +const int BT_ENTRY_MAX = 25; +struct CrashInfo { + int backtrace_entries = 0; + void* backtrace[BT_ENTRY_MAX]; + int signal = 0; +}; + +CrashInfo crash_info; + +std::atomic crashed = false; +std::atomic crashlog_ready = false; +std::atomic shutdown = false; + +// Use eventfd for async-signal safe waiting +int crashlog_complete = -1; + +void flag_set(std::atomic_bool &atom) { + atom.store(true); + atom.notify_all(); +} +void flag_wait(std::atomic_bool &atom) { + atom.wait(false); +} + +void signal_crashlog_complete() { + if (crashlog_complete == -1) + return; + uint64_t v = 1; + [[maybe_unused]] auto _ = write(crashlog_complete, &v, sizeof(v)); +} + +std::thread crashlog_thread; + +// Force this method to be inlined so that it doesn't create a stack frame +[[gnu::always_inline]] inline void handle_signal_internal(int sig) { + if (shutdown.load() || crashed.exchange(true) || crashlog_ready.load()) { + // Ensure the signal handler doesn't try to write a crashlog + // whilst the crashlog thread is unavailable. + std::quick_exit(1); + } + crash_info.signal = sig; + crash_info.backtrace_entries = backtrace(crash_info.backtrace, BT_ENTRY_MAX); + + // Signal saving of crashlog + flag_set(crashlog_ready); + // Wait for completion via eventfd read, if fd isn't valid, bail + if (crashlog_complete != -1) { + [[maybe_unused]] uint64_t v; + [[maybe_unused]] auto _ = read(crashlog_complete, &v, sizeof(v)); + } + std::quick_exit(1); +} + +extern "C" void dfhack_crashlog_handle_signal(int sig) { + handle_signal_internal(sig); +} + +void dfhack_crashlog_handle_terminate() { + handle_signal_internal(0); +} + +std::string signal_name(int sig) { + switch (sig) { + case SIGINT: + return "SIGINT"; + case SIGILL: + return "SIGILL"; + case SIGABRT: + return "SIGABRT"; + case SIGFPE: + return "SIGFPE"; + case SIGSEGV: + return "SIGSEGV"; + case SIGTERM: + return "SIGTERM"; + } + return ""; +} + +std::filesystem::path get_crashlog_path() { + std::time_t time = std::time(nullptr); + std::tm* tm = std::localtime(&time); + + std::string timestamp = "unknown"; + if (tm) { + char stamp[64]; + std::size_t out = strftime(&stamp[0], 63, "%Y-%m-%d-%H-%M-%S", tm); + if (out != 0) + timestamp = stamp; + } + + std::filesystem::path dir = "crashlog"; + std::error_code err; + std::filesystem::create_directories(dir, err); + + std::filesystem::path log_path = dir / ("crash_" + timestamp + ".txt"); + return log_path; +} + +void dfhack_save_crashlog() { + char** backtrace_strings = backtrace_symbols(crash_info.backtrace, crash_info.backtrace_entries); + try { + std::filesystem::path crashlog_path = get_crashlog_path(); + std::ofstream crashlog(crashlog_path); + + crashlog << "Dwarf Fortress Linux has crashed!" << "\n"; + crashlog << "Dwarf Fortress Version " << DFHack::Version::df_version() << "\n"; + crashlog << "DFHack Version " << DFHack::Version::dfhack_version() << "\n\n"; + + std::string signal = signal_name(crash_info.signal); + if (!signal.empty()) { + crashlog << "Signal " << signal << "\n"; + } + + if (crash_info.backtrace_entries >= 2 && backtrace_strings != nullptr) { + // Skip the first backtrace entry as it will always be dfhack_crashlog_handle_(signal|terminate) + for (int i = 1; i < crash_info.backtrace_entries; i++) { + crashlog << i - 1 << "> " << backtrace_strings[i] << "\n"; + } + } else { + // Make it clear if no relevant backtrace was able to be obtained + crashlog << "Failed to obtain relevant backtrace\n"; + } + } catch (...) {} + + free(backtrace_strings); +} + +void dfhack_crashlog_thread() { + // Wait for activation signal + flag_wait(crashlog_ready); + if (shutdown.load()) // Shutting down gracefully, end thread. + return; + + dfhack_save_crashlog(); + signal_crashlog_complete(); + std::quick_exit(1); +} + +std::terminate_handler term_handler = nullptr; + +const int desired_signals[3] = {SIGSEGV,SIGILL,SIGABRT}; +namespace DFHack { + void dfhack_crashlog_init() { + // Initialize eventfd flag + crashlog_complete = eventfd(0, EFD_CLOEXEC); + + crashlog_thread = std::thread(dfhack_crashlog_thread); + + for (int signal : desired_signals) { + std::signal(signal, dfhack_crashlog_handle_signal); + } + term_handler = std::set_terminate(dfhack_crashlog_handle_terminate); + + // https://sourceware.org/glibc/manual/latest/html_mono/libc.html#index-backtrace-1 + // backtrace is AsyncSignal-Unsafe due to dynamic loading of libgcc_s + // Using it here ensures it is loaded before use in the signal handler. + [[maybe_unused]] int _ = backtrace(crash_info.backtrace, 1); + } + + void dfhack_crashlog_shutdown() { + shutdown.exchange(true); + for (int signal : desired_signals) { + std::signal(signal, SIG_DFL); + } + std::set_terminate(term_handler); + + // Shutdown the crashlog thread. + flag_set(crashlog_ready); + crashlog_thread.join(); + + // If the signal handler is somehow running whilst here, let it terminate + signal_crashlog_complete(); + if (crashlog_complete != -1) + close(crashlog_complete); // Close fd + return; + } +} diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index 5d2386282e..880a8b93a5 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -27,6 +27,7 @@ distribution. #include #include #include +#include #include "MemAccess.h" #include "Core.h" @@ -86,27 +87,43 @@ void *enum_identity::do_allocate() const { return p; } -/* The order of global object constructor calls is - * undefined between compilation units. Therefore, - * this list has to be plain data, so that it gets - * initialized by the loader in the initial mmap. - */ -compound_identity *compound_identity::list = NULL; -std::vector compound_identity::top_scope; +/****** COMPOUND IDENTITIES ******/ -compound_identity::compound_identity(size_t size, TAllocateFn alloc, - const compound_identity *scope_parent, const char *dfhack_name) - : constructed_identity(size, alloc), dfhack_name(dfhack_name), scope_parent(const_cast(scope_parent)) // fixme +decltype(compound_identity::list) compound_identity::list = nullptr; +decltype(compound_identity::parent_map) compound_identity::parent_map = nullptr; +decltype(compound_identity::children_map) compound_identity::children_map = nullptr; +decltype(compound_identity::top_scope) compound_identity::top_scope = nullptr; + +void compound_identity::ensure_compound_identity_init() { - next = list; list = this; + static std::once_flag compound_identity_init_flag; + std::call_once(compound_identity_init_flag, [] () { + list = new (std::remove_pointer::type)(); + parent_map = new (std::remove_pointer::type)(); + children_map = new (std::remove_pointer::type)(); + top_scope = new std::vector(); + }); } -void compound_identity::doInit(Core *) +compound_identity::compound_identity(size_t size, TAllocateFn alloc, + const compound_identity* scope_parent, const char* dfhack_name) + : constructed_identity(size, alloc), dfhack_name(dfhack_name), scope_parent(scope_parent) { + ensure_compound_identity_init(); + if (scope_parent) - scope_parent->scope_children.push_back(this); + { + (*parent_map)[this] = scope_parent; + (*children_map)[scope_parent].push_back(this); + } else - top_scope.push_back(this); + (*top_scope).push_back(this); + + list->push_back(this); +} + +void compound_identity::doInit(Core *) const +{ } const std::string compound_identity::getFullName() const @@ -124,10 +141,10 @@ void compound_identity::Init(Core *core) if (!known_mutex) known_mutex = new std::mutex(); - // This cannot be done in the constructors, because - // they are called in an undefined order. - for (compound_identity *p = list; p; p = p->next) - p->doInit(core); + // do any late initialization required for compound identities + + for (auto ci : *list) + ci->doInit(core); } bitfield_identity::bitfield_identity(size_t size, @@ -176,27 +193,44 @@ enum_identity::ComplexData::ComplexData(std::initializer_list values) } } +/****** STRUCT IDENTITIES ******/ + +decltype(struct_identity::parent_map) struct_identity::parent_map = nullptr; +decltype(struct_identity::children_map) struct_identity::children_map = nullptr; + +void struct_identity::ensure_struct_identity_init() +{ + static std::once_flag struct_identity_init_flag; + std::call_once(struct_identity_init_flag, []() { + parent_map = new (std::remove_pointer::type)(); + children_map = new (std::remove_pointer::type)(); + }); +} + + + struct_identity::struct_identity(size_t size, TAllocateFn alloc, const compound_identity *scope_parent, const char *dfhack_name, const struct_identity *parent, const struct_field_info *fields) : compound_identity(size, alloc, scope_parent, dfhack_name), - parent(const_cast(parent)), has_children(false), fields(fields) + fields(fields) { + ensure_struct_identity_init(); + if (parent) + { + (*parent_map)[this] = parent; + (*children_map)[parent].push_back(this); + } } -void struct_identity::doInit(Core *core) +void struct_identity::doInit(Core *core) const { compound_identity::doInit(core); - - if (parent) { - parent->children.push_back(this); - parent->has_children = true; - } } bool struct_identity::is_subclass(const struct_identity *actual) const { - if (!has_children && actual != this) + if (!hasChildren() && actual != this) return false; for (; actual; actual = actual->getParent()) @@ -228,23 +262,42 @@ const std::string bit_container_identity::getFullName(const type_identity *) con const std::string df::buffer_container_identity::getFullName(const type_identity *item) const { return (item ? item->getFullName() : std::string("void")) + - (size > 0 ? stl_sprintf("[%d]", size) : std::string("[]")); + (size > 0 ? fmt::format("[{}]", size) : std::string("[]")); } union_identity::union_identity(size_t size, const TAllocateFn alloc, - compound_identity *scope_parent, const char *dfhack_name, - struct_identity *parent, const struct_field_info *fields) + const compound_identity *scope_parent, const char *dfhack_name, + const struct_identity *parent, const struct_field_info *fields) : struct_identity(size, alloc, scope_parent, dfhack_name, parent, fields) { } +/****** VIRTUAL IDENTITIES ******/ + +decltype(virtual_identity::name_lookup) virtual_identity::name_lookup = nullptr; +decltype(virtual_identity::known) virtual_identity::known = nullptr; +decltype(virtual_identity::vtable_ptr_map) virtual_identity::vtable_ptr_map = nullptr; +decltype(virtual_identity::interpose_list_map) virtual_identity::interpose_list_map = nullptr; + +void virtual_identity::ensure_virtual_identity_init() +{ + static std::once_flag virtual_identity_init_flag; + std::call_once(virtual_identity_init_flag, []() { + name_lookup = new (std::remove_pointer::type)(); + known = new (std::remove_pointer::type)(); + vtable_ptr_map = new (std::remove_pointer::type)(); + interpose_list_map = new (std::remove_pointer::type)(); + }); +} + virtual_identity::virtual_identity(size_t size, const TAllocateFn alloc, const char *dfhack_name, const char *original_name, const virtual_identity *parent, const struct_field_info *fields, bool is_plugin) : struct_identity(size, alloc, NULL, dfhack_name, parent, fields), original_name(original_name), - vtable_ptr(NULL), is_plugin(is_plugin) + is_plugin(is_plugin) { + ensure_virtual_identity_init(); // Plugins are initialized after Init was called, so they need to be added to the name table here if (is_plugin) { @@ -252,15 +305,10 @@ virtual_identity::virtual_identity(size_t size, const TAllocateFn alloc, } } -/* Vtable name to identity lookup. */ -static std::map name_lookup; - -/* Vtable pointer to identity lookup. */ -std::map virtual_identity::known; - virtual_identity::~virtual_identity() { // Remove interpose entries, so that they don't try accessing this object later + auto& interpose_list = (*interpose_list_map)[this]; for (auto it = interpose_list.begin(); it != interpose_list.end(); ++it) if (it->second) it->second->on_host_delete(this); @@ -269,75 +317,79 @@ virtual_identity::~virtual_identity() // Remove global lookup table entries if we're from a plugin if (is_plugin) { - name_lookup.erase(getOriginalName()); + (*name_lookup).erase(getOriginalName()); - if (vtable_ptr) - known.erase(vtable_ptr); + if (vtable_ptr()) + (*known).erase(vtable_ptr()); } } -void virtual_identity::doInit(Core *core) +void virtual_identity::doInit(Core *core) const { struct_identity::doInit(core); auto vtname = getOriginalName(); - name_lookup[vtname] = this; + (*name_lookup)[vtname] = this; - vtable_ptr = core->vinfo->getVTable(vtname); + auto vtable_ptr = core->vinfo->getVTable(vtname); if (vtable_ptr) - known[vtable_ptr] = this; + { + (*known)[vtable_ptr] = this; + (*vtable_ptr_map)[this] = vtable_ptr; + } } -virtual_identity *virtual_identity::find(const std::string &name) +const virtual_identity *virtual_identity::find(std::string_view name) { - auto name_it = name_lookup.find(name); + auto name_it = (*name_lookup).find(name); - return (name_it != name_lookup.end()) ? name_it->second : NULL; + return (name_it != (*name_lookup).end()) ? name_it->second : nullptr; } -virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) +const virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) { - if (!instance_ptr) return NULL; + if (!instance_ptr) return nullptr; return find(get_vtable(instance_ptr)); } -virtual_identity *virtual_identity::find(void *vtable) +const virtual_identity *virtual_identity::find(void *vtable) { if (!vtable || !known_mutex) - return NULL; + return nullptr; + + ensure_virtual_identity_init(); // Actually, a reader/writer lock would be sufficient, // since the table is only written once per class. std::lock_guard lock(*known_mutex); - std::map::iterator it = known.find(vtable); - - if (it != known.end()) + auto it = (*known).find(vtable); + if (it != (*known).end()) return it->second; // If using a reader/writer lock, re-grab as write here, and recheck Core &core = Core::getInstance(); - std::string name = core.p->doReadClassName(vtable); + std::string name = core.p->readClassName(vtable); - auto name_it = name_lookup.find(name); - if (name_it != name_lookup.end()) { - virtual_identity *p = name_it->second; + auto name_it = (*name_lookup).find(name); + if (name_it != (*name_lookup).end()) { + const virtual_identity *p = name_it->second; - if (p->vtable_ptr && p->vtable_ptr != vtable) { + if (p->vtable_ptr() && p->vtable_ptr() != vtable) { std::cerr << "Conflicting vtable ptr for class '" << p->getName() << "': found 0x" << std::hex << uintptr_t(vtable) - << ", previous 0x" << uintptr_t(p->vtable_ptr) << std::dec << std::endl; + << ", previous 0x" << uintptr_t(p->vtable_ptr()) << std::dec << std::endl; abort(); - } else if (!p->vtable_ptr) { + } else if (!p->vtable_ptr()) { uintptr_t pv = uintptr_t(vtable); pv -= Core::getInstance().vinfo->getRebaseDelta(); std::cerr << "" << std::endl; } - known[vtable] = p; - p->vtable_ptr = vtable; + (*known)[vtable] = p; + (*vtable_ptr_map)[p] = vtable; return p; } @@ -346,14 +398,15 @@ virtual_identity *virtual_identity::find(void *vtable) << std::hex << uintptr_t(vtable) << std::dec << std::endl; } - known[vtable] = NULL; - return NULL; + (*known).erase(vtable); + return nullptr; } -void virtual_identity::adjust_vtable(virtual_ptr obj, virtual_identity *main) +void virtual_identity::adjust_vtable(virtual_ptr obj, const virtual_identity *main) const { - if (vtable_ptr) { - *(void**)obj = vtable_ptr; + auto vp = vtable_ptr(); + if (vp) { + *(void**)obj = vp; return; } @@ -366,10 +419,10 @@ void virtual_identity::adjust_vtable(virtual_ptr obj, virtual_identity *main) virtual_ptr virtual_identity::clone(virtual_ptr obj) { - virtual_identity *id = get(obj); - if (!id) return NULL; + const virtual_identity *id = get(obj); + if (!id) return nullptr; virtual_ptr copy = id->instantiate(); - if (!copy) return NULL; + if (!copy) return nullptr; id->do_copy(copy, obj); return copy; } @@ -429,7 +482,7 @@ void DFHack::bitfieldToString(std::vector *pvec, const void *p, std::string name = format_key(items[i].name, i); if (items[i].size > 1) - name += stl_sprintf("=%u", value); + name += fmt::format("={}", value); pvec->push_back(name); } diff --git a/library/DataIdentity.cpp b/library/DataIdentity.cpp index a385915d92..0a797c2069 100644 --- a/library/DataIdentity.cpp +++ b/library/DataIdentity.cpp @@ -1,14 +1,21 @@ -#include +#include "DataIdentity.h" + +#include "BitArray.h" +#include "DataDefs.h" #include +#include +#include #include +#include +#include +#include #include +#include #include -#include #include +#include -#include "DataFuncs.h" -#include "DataIdentity.h" // the space after the uses of "type" in OPAQUE_IDENTITY_TRAITS_NAME is _required_ // without it the macro generates a syntax error when type is a template specification @@ -19,7 +26,7 @@ namespace df { #define INTEGER_IDENTITY_TRAITS(type, name) NUMBER_IDENTITY_TRAITS(integer, type, name) #define FLOAT_IDENTITY_TRAITS(type) NUMBER_IDENTITY_TRAITS(float, type, #type) #define OPAQUE_IDENTITY_TRAITS_NAME(name, ...) \ - const opaque_identity identity_traits<__VA_ARGS__ >::identity(sizeof(__VA_ARGS__), allocator_noassign_fn<__VA_ARGS__ >, name) + const opaque_identity identity_traits<__VA_ARGS__ >::identity(sizeof(__VA_ARGS__), allocator_fn<__VA_ARGS__ >, name) #define OPAQUE_IDENTITY_TRAITS(...) OPAQUE_IDENTITY_TRAITS_NAME(#__VA_ARGS__, __VA_ARGS__ ) INTEGER_IDENTITY_TRAITS(char, "char"); @@ -33,6 +40,7 @@ namespace df { INTEGER_IDENTITY_TRAITS(unsigned long, "unsigned long"); INTEGER_IDENTITY_TRAITS(long long, "int64_t"); INTEGER_IDENTITY_TRAITS(unsigned long long, "uint64_t"); + INTEGER_IDENTITY_TRAITS(wchar_t, "wchar_t"); FLOAT_IDENTITY_TRAITS(float); FLOAT_IDENTITY_TRAITS(double); @@ -57,6 +65,7 @@ namespace df { OPAQUE_IDENTITY_TRAITS(std::optional >); OPAQUE_IDENTITY_TRAITS(std::variant >); OPAQUE_IDENTITY_TRAITS(std::weak_ptr); + OPAQUE_IDENTITY_TRAITS(wchar_t*); const buffer_container_identity buffer_container_identity::base_instance; diff --git a/library/DataStatics.cpp b/library/DataStatics.cpp index fe60e54544..af7613cdaa 100644 --- a/library/DataStatics.cpp +++ b/library/DataStatics.cpp @@ -22,8 +22,8 @@ namespace { DFHack::VersionInfo *global_table_ = DFHack::Core::getInstance().vinfo.get(); \ void * tmp_; -#define INIT_GLOBAL_FUNCTION_ITEM(type,name) \ - if (global_table_->getAddress(#name,tmp_)) name = (type*)tmp_; +#define INIT_GLOBAL_FUNCTION_ITEM(name, ...) \ + if (global_table_->getAddress(#name,tmp_)) name = (__VA_ARGS__*)tmp_; #define TID(type) (&identity_traits< type >::identity) diff --git a/library/Debug.cpp b/library/Debug.cpp index dafbeb5ce3..50bef6e538 100644 --- a/library/Debug.cpp +++ b/library/Debug.cpp @@ -66,9 +66,6 @@ void DebugManager::unregisterCategory(DebugCategory& cat) DebugRegisterBase::DebugRegisterBase(DebugCategory* cat) { - // Make sure Core lives at least as long any DebugCategory to - // allow debug prints until all Debugcategories has been destructed - Core::getInstance(); DebugManager::getInstance().registerCategory(*cat); } diff --git a/library/Error.cpp b/library/Error.cpp index 3cd0eb31d6..9abb9ea7c9 100644 --- a/library/Error.cpp +++ b/library/Error.cpp @@ -1,6 +1,9 @@ #include "Error.h" +#include "Format.h" #include "MiscUtils.h" +#include + using namespace DFHack::Error; inline std::string safe_str(const char *s) @@ -24,7 +27,7 @@ VTableMissing::VTableMissing(const char *name) {} SymbolsXmlParse::SymbolsXmlParse(const char* desc, int id, int row, int col) - :AllSymbols(stl_sprintf("error %d: %s, at row %d col %d", id, desc, row, col)), + :AllSymbols(fmt::format("error {}: {}, at row {} col {}", id, desc, row, col)), desc(safe_str(desc)), id(id), row(row), col(col) {} diff --git a/library/Hooks.cpp b/library/Hooks.cpp index 951472eba6..0bb6b04426 100644 --- a/library/Hooks.cpp +++ b/library/Hooks.cpp @@ -3,10 +3,45 @@ #include "df/gamest.h" +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include +# include +#else +# include +#endif + static bool disabled = false; DFhackCExport const int32_t dfhooks_priority = 100; +static std::unique_ptr core_instance; + +static std::filesystem::path getModulePath() +{ +#ifdef _WIN32 + HMODULE module = nullptr; + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)getModulePath, &module); + if (!module) return std::filesystem::path(); // should never happen, but just in case, return an empty path instead of crashing + + wchar_t path[MAX_PATH]; + GetModuleFileNameW(module, path, MAX_PATH); + return std::filesystem::path(path); +#else + Dl_info info; + dladdr((const void*)getModulePath, &info); + return std::filesystem::path(info.dli_fname); +#endif +} + +static std::filesystem::path basepath{getModulePath()}; + +// called by the chainloader before the main thread is initialized and before any other hooks are called. +DFhackCExport void dfhooks_preinit(std::filesystem::path dllpath) +{ + basepath = dllpath.parent_path(); +} + // called from the main thread before the simulation thread is started // and the main event loop is initiated DFhackCExport void dfhooks_init() { @@ -16,8 +51,11 @@ DFhackCExport void dfhooks_init() { return; } + // construct DFHack core instance + core_instance = std::make_unique(); + // we need to init DF globals before we can check the commandline - if (!DFHack::Core::getInstance().InitMainThread() || !df::global::game) { + if (!core_instance->InitMainThread(std::filesystem::canonical(basepath)) || !df::global::game) { // we don't set disabled to true here so symbol generation can work return; } @@ -26,6 +64,8 @@ DFhackCExport void dfhooks_init() { if (cmdline.find("--disable-dfhack") != std::string::npos) { fprintf(stderr, "dfhack: --disable-dfhack specified on commandline; disabling\n"); disabled = true; + core_instance->Shutdown(); + core_instance.reset(); return; } @@ -36,14 +76,16 @@ DFhackCExport void dfhooks_init() { DFhackCExport void dfhooks_shutdown() { if (disabled) return; - DFHack::Core::getInstance().Shutdown(); + core_instance->Shutdown(); + // release DFHack core instance + core_instance.reset(); } // called from the simulation thread in the main event loop DFhackCExport void dfhooks_update() { if (disabled) return; - DFHack::Core::getInstance().Update(); + core_instance->Update(); } // called from the simulation thread just before adding the macro @@ -59,7 +101,7 @@ DFhackCExport void dfhooks_prerender() { DFhackCExport bool dfhooks_sdl_event(SDL_Event* event) { if (disabled) return false; - return DFHack::Core::getInstance().DFH_SDL_Event(event); + return core_instance->DFH_SDL_Event(event); } // called from the main thread just after setting mouse state in gps and just @@ -68,6 +110,7 @@ DFhackCExport void dfhooks_sdl_loop() { if (disabled) return; // TODO: wire this up to the new SDL-based console once it is merged + core_instance->DFH_SDL_Loop(); } // called from the main thread for each utf-8 char read from the ncurses input @@ -77,5 +120,5 @@ DFhackCExport void dfhooks_sdl_loop() { DFhackCExport bool dfhooks_ncurses_key(int key) { if (disabled) return false; - return DFHack::Core::getInstance().DFH_ncurses_key(key); + return core_instance->DFH_ncurses_key(key); } diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 1989b9a78b..d321ca7891 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -36,6 +36,7 @@ distribution. #include "LuaTools.h" #include "LuaWrapper.h" #include "md5wrapper.h" +#include "MemoryPatcher.h" #include "MiscUtils.h" #include "PluginManager.h" @@ -47,6 +48,7 @@ distribution. #include "modules/EventManager.h" #include "modules/Filesystem.h" #include "modules/Gui.h" +#include "modules/Hotkey.h" #include "modules/Items.h" #include "modules/Job.h" #include "modules/Kitchen.h" @@ -90,6 +92,7 @@ distribution. #include "df/job_item.h" #include "df/job_material_category.h" #include "df/language_word_table.h" +#include "df/manager_order.h" #include "df/material.h" #include "df/map_block.h" #include "df/nemesis_record.h" @@ -1356,6 +1359,7 @@ static uint32_t getTickCount() { return Core::getInstance().p->getTickCount(); } static std::filesystem::path getDFPath() { return Core::getInstance().p->getPath(); } static std::filesystem::path getHackPath() { return Core::getInstance().getHackPath(); } +static std::filesystem::path getConfigPath() { return Core::getInstance().getConfigPath(); } static bool isWorldLoaded() { return Core::getInstance().isWorldLoaded(); } static bool isMapLoaded() { return Core::getInstance().isMapLoaded(); } @@ -1381,6 +1385,7 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP(getDFPath), WRAP(getTickCount), WRAP(getHackPath), + WRAP(getConfigPath), WRAP(isWorldLoaded), WRAP(isMapLoaded), WRAP(isSiteLoaded), @@ -1866,6 +1871,123 @@ static const luaL_Reg dfhack_gui_funcs[] = { { NULL, NULL } }; +/***** Hotkey module *****/ +static bool hotkey_addKeybind(const std::string spec, const std::string cmd) { + auto hotkey_mgr = Core::getInstance().getHotkeyManager(); + if (!hotkey_mgr) return false; + return hotkey_mgr->addKeybind(spec, cmd); +} + +static bool hotkey_isDisruptiveKeybind(const std::string spec) { + auto key = Hotkey::KeySpec::parse(spec); + if (!key.has_value()) + return true; + return key.value().isDisruptive(); +} + +static int hotkey_requestKeybindingInput(lua_State *L) { + auto hotkey_mgr = Core::getInstance().getHotkeyManager(); + if (!hotkey_mgr) return 0; + bool cancel = false; + if (lua_gettop(L) == 1) + cancel = lua_toboolean(L, -1); + hotkey_mgr->requestKeybindingInput(cancel); + return 0; +} + +static int hotkey_getKeybindingInput(lua_State *L) { + auto hotkey_mgr = Core::getInstance().getHotkeyManager(); + auto input = hotkey_mgr->getKeybindingInput(); + + if (input.empty()) { + lua_pushnil(L); + } else { + lua_pushlstring(L, input.data(), input.size()); + } + return 1; +} + +static int hotkey_removeKeybind(lua_State *L) { + auto hotkey_mgr = Core::getInstance().getHotkeyManager(); + if (!hotkey_mgr) { + lua_pushboolean(L, false); + return 1; + } + + bool res = false; + switch (lua_gettop(L)) { + case 1: + luaL_checkstring(L, -1); + res = hotkey_mgr->removeKeybind(lua_tostring(L, -1)); + break; + case 2: + luaL_checkstring(L, -2); + res = hotkey_mgr->removeKeybind(lua_tostring(L, -2), lua_toboolean(L, -1)); + break; + case 3: + luaL_checkstring(L, -3); + luaL_checkstring(L, -1); + res = hotkey_mgr->removeKeybind( + lua_tostring(L, -3), + lua_toboolean(L, -2), + lua_tostring(L, -1) + ); + break; + } + + lua_pushboolean(L, res); + return 1; +} + +void hotkey_pushBindArray(lua_State *L, const std::vector& binds) { + lua_createtable(L, binds.size(), 0); + int i = 1; + for (const auto& bind : binds) { + lua_createtable(L, 0, 2); + + lua_pushlstring(L, "spec", 4); + auto spec_str = bind.spec.toString(true); + lua_pushlstring(L, spec_str.data(), spec_str.size()); + lua_settable(L, -3); + + lua_pushlstring(L, "command", 7); + lua_pushlstring(L, bind.cmdline.data(), bind.cmdline.size()); + lua_settable(L, -3); + lua_rawseti(L, -2, i++); + } +} + +static int hotkey_listActiveKeybinds(lua_State *L) { + auto hotkey_mgr = Core::getInstance().getHotkeyManager(); + auto binds = hotkey_mgr->listActiveKeybinds(); + + hotkey_pushBindArray(L, binds); + return 1; +} + +static int hotkey_listAllKeybinds(lua_State *L) { + auto hotkey_mgr = Core::getInstance().getHotkeyManager(); + auto binds = hotkey_mgr->listAllKeybinds(); + + hotkey_pushBindArray(L, binds); + return 1; +} + +static const luaL_Reg dfhack_hotkey_funcs[] = { + { "removeKeybind", hotkey_removeKeybind }, + { "listActiveKeybinds", hotkey_listActiveKeybinds }, + { "listAllKeybinds", hotkey_listAllKeybinds }, + { "requestKeybindingInput", hotkey_requestKeybindingInput }, + { "getKeybindingInput", hotkey_getKeybindingInput }, + { NULL, NULL } +}; + +static const LuaWrapper::FunctionReg dfhack_hotkey_module[] = { + WRAPN(addKeybind, hotkey_addKeybind), + WRAPN(isDisruptiveKeybind, hotkey_isDisruptiveKeybind), + { NULL, NULL } +}; + /***** Job module *****/ static bool jobEqual(const df::job *job1, const df::job *job2) @@ -1899,6 +2021,7 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { WRAPM(Job,isSuitableItem), WRAPM(Job,isSuitableMaterial), WRAPM(Job,getName), + WRAPM(Job,getManagerOrderName), WRAPM(Job,linkIntoWorld), WRAPM(Job,removePostings), WRAPM(Job,disconnectJobItem), @@ -2532,6 +2655,7 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { WRAPM(Maps, getWalkableGroup), WRAPM(Maps, canWalkBetween), WRAPM(Maps, spawnFlow), + WRAPM(Maps, addBlockColumns), WRAPN(hasTileAssignment, hasTileAssignment), WRAPN(getTileAssignment, getTileAssignment), WRAPN(setTileAssignment, setTileAssignment), @@ -2637,6 +2761,7 @@ static int maps_setTileAquifer(lua_State* L) case 1: Lua::CheckDFAssign(L, &p, 1); rv = Maps::setTileAquifer(p); + break; case 2: Lua::CheckDFAssign(L, &p, 1); rv = Maps::setTileAquifer(p, lua_toboolean(L, 2)); @@ -2661,6 +2786,40 @@ static int maps_removeTileAquifer(lua_State* L) return 1; } +static int maps_addMaterialSpatter(lua_State *L) +{ + int32_t rv; + df::coord pos; + + Lua::CheckDFAssign(L, &pos, 1); + int16_t mat = lua_tointeger(L, 2); + int32_t matg = lua_tointeger(L, 3); + df::matter_state state = (df::matter_state)lua_tointeger(L, 4); + int32_t amount = lua_tointeger(L, 5); + rv = Maps::addMaterialSpatter(pos, mat, matg, state, amount); + + lua_pushinteger(L, rv); + return 1; +} + +static int maps_addItemSpatter(lua_State *L) +{ + int32_t rv; + df::coord pos; + + Lua::CheckDFAssign(L, &pos, 1); + df::item_type i_type = (df::item_type)lua_tointeger(L, 2); + int16_t i_subtype = lua_tointeger(L, 3); + int16_t i_subcat1 = lua_tointeger(L, 4); + int32_t i_subcat2 = lua_tointeger(L, 5); + int32_t print_variant = lua_tointeger(L, 6); + int32_t amount = lua_tointeger(L, 7); + rv = Maps::addItemSpatter(pos, i_type, i_subtype, i_subcat1, i_subcat2, print_variant, amount); + + lua_pushinteger(L, rv); + return 1; +} + static const luaL_Reg dfhack_maps_funcs[] = { { "isValidTilePos", maps_isValidTilePos }, { "isTileVisible", maps_isTileVisible }, @@ -2676,6 +2835,8 @@ static const luaL_Reg dfhack_maps_funcs[] = { { "isTileHeavyAquifer", maps_isTileHeavyAquifer }, { "setTileAquifer", maps_setTileAquifer }, { "removeTileAquifer", maps_removeTileAquifer }, + { "addMaterialSpatter", maps_addMaterialSpatter }, + { "addItemSpatter", maps_addItemSpatter }, { NULL, NULL } }; @@ -2968,6 +3129,34 @@ static int screen_readTile(lua_State *L) return 1; } +static int screen_paintMapPortTile(lua_State *L) +{ + Pen pen; + Lua::CheckPen(L, &pen, 1); + int x = luaL_checkint(L, 2); + int y = luaL_checkint(L, 3); + if (lua_gettop(L) >= 4 && !lua_isnil(L, 4)) + { + if (lua_type(L, 4) == LUA_TSTRING) + pen.ch = lua_tostring(L, 4)[0]; + else + pen.ch = luaL_checkint(L, 4); + } + if (lua_gettop(L) >= 5 && !lua_isnil(L, 5)) + pen.tile = luaL_checkint(L, 5); + lua_pushboolean(L, Screen::paintMapPortTile(pen, x, y)); + return 1; +} + +static int screen_readMapPortTile(lua_State *L) +{ + int x = luaL_checkint(L, 1); + int y = luaL_checkint(L, 2); + Pen pen = Screen::readMapPortTile(x, y, &df::graphic_map_portst::screentexpos_site); + Lua::Push(L, pen); + return 1; +} + static int screen_paintString(lua_State *L) { Pen pen; @@ -3155,6 +3344,8 @@ static const luaL_Reg dfhack_screen_funcs[] = { { "getWindowSize", screen_getWindowSize }, { "paintTile", screen_paintTile }, { "readTile", screen_readTile }, + { "paintMapPortTile", screen_paintMapPortTile }, + { "readMapPortTile", screen_readMapPortTile }, { "paintString", screen_paintString }, { "fillRect", screen_fillRect }, { "findGraphicsTile", screen_findGraphicsTile }, @@ -3523,7 +3714,7 @@ static void setPreferredNumberFormat(color_ostream & out, int32_t type_int) { set_preferred_number_format_type(type); break; default: - WARN(luaapi, out).print("invalid number format enum value: %d\n", type_int); + WARN(luaapi, out).print("invalid number format enum value: {}\n", type_int); } } @@ -3607,7 +3798,7 @@ static int internal_setAddress(lua_State *L) // Print via printerr, so that it is definitely logged to stderr.log. uintptr_t iaddr = addr - Core::getInstance().vinfo->getRebaseDelta(); - fprintf(stderr, "Setting global '%s' to %p (%p)\n", name.c_str(), (void*)addr, (void*)iaddr); + fmt::print(stderr, "Setting global '{}' to {} ({})\n", name, (addr), (void*)iaddr); fflush(stderr); return 1; @@ -3963,6 +4154,9 @@ static int internal_getModifiers(lua_State *L) lua_pushstring(L, "alt"); lua_pushboolean(L, modstate & DFH_MOD_ALT); lua_settable(L, -3); + lua_pushstring(L, "super"); + lua_pushboolean(L, modstate & DFH_MOD_SUPER); + lua_settable(L, -3); return 1; } @@ -4304,6 +4498,7 @@ void OpenDFHackApi(lua_State *state) luaL_setfuncs(state, dfhack_funcs, 0); OpenModule(state, "translation", dfhack_translation_module); OpenModule(state, "gui", dfhack_gui_module, dfhack_gui_funcs); + OpenModule(state, "hotkey", dfhack_hotkey_module, dfhack_hotkey_funcs); OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); OpenModule(state, "textures", dfhack_textures_funcs); OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs); diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 95f8017dd8..56c5079838 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -244,12 +244,12 @@ static void check_valid_ptr_index(lua_State *state, int val_index) } } -static void signal_typeid_error(color_ostream *out, lua_State *state, - const type_identity *type, const char *msg, +static void signal_typeid_error(color_ostream* out, lua_State* state, + const type_identity* type, std::string_view msg, int val_index, bool perr, bool signal) { std::string typestr = type ? type->getFullName() : "any pointer"; - std::string error = stl_sprintf(msg, typestr.c_str()); + std::string error = fmt::vformat(msg, fmt::make_format_args(typestr)); //FIXME: C++26 if (signal) { @@ -261,7 +261,7 @@ static void signal_typeid_error(color_ostream *out, lua_State *state, else if (perr) { if (out) - out->printerr("%s", error.c_str()); + out->printerr("{}", error); else dfhack_printerr(state, error); } @@ -282,7 +282,7 @@ void *DFHack::Lua::CheckDFObject(lua_State *state, const type_identity *type, in void *rv = get_object_internal(state, type, val_index, exact_type, false); if (!rv) - signal_typeid_error(NULL, state, type, "invalid pointer type; expected: %s", + signal_typeid_error(NULL, state, type, "invalid pointer type; expected: {}", val_index, false, true); return rv; @@ -366,9 +366,9 @@ static int lua_dfhack_print(lua_State *S) { std::string str = lua_print_fmt(S); if (color_ostream *out = Lua::GetOutput(S)) - out->print("%s", str.c_str());//*out << str; + out->print("{}", str); else - Core::print("%s", str.c_str()); + Core::print("{}", str); return 0; } @@ -378,16 +378,16 @@ static int lua_dfhack_println(lua_State *S) if (color_ostream *out = Lua::GetOutput(S)) *out << str << std::endl; else - Core::print("%s\n", str.c_str()); + Core::print("{}\n", str); return 0; } void dfhack_printerr(lua_State *S, const std::string &str) { if (color_ostream *out = Lua::GetOutput(S)) - out->printerr("%s\n", str.c_str()); + out->printerr("{}\n", str); else - Core::printerr("%s\n", str.c_str()); + Core::printerr("{}\n", str); } static int lua_dfhack_printerr(lua_State *S) @@ -563,7 +563,7 @@ static void report_error(lua_State *L, color_ostream *out = NULL, bool pop = fal assert(msg); if (out) - out->printerr("%s\n", msg); + out->printerr("{}\n", msg); else dfhack_printerr(L, msg); @@ -842,7 +842,7 @@ bool DFHack::Lua::CallLuaModuleFunction(color_ostream &out, lua_State *L, if (!lua_checkstack(L, 1 + nargs) || !Lua::PushModulePublic(out, L, module_name, fn_name)) { if (perr) - out.printerr("Failed to load %s Lua code\n", module_name); + out.printerr("Failed to load {} Lua code\n", module_name); return false; } @@ -850,7 +850,7 @@ bool DFHack::Lua::CallLuaModuleFunction(color_ostream &out, lua_State *L, if (!Lua::SafeCall(out, L, nargs, nres, perr)) { if (perr) - out.printerr("Failed Lua call to '%s.%s'\n", module_name, fn_name); + out.printerr("Failed Lua call to '{}.{}'\n", module_name, fn_name); return false; } @@ -1077,7 +1077,7 @@ bool DFHack::Lua::Require(color_ostream &out, lua_State *state, } static bool doAssignDFObject(color_ostream *out, lua_State *state, - type_identity *type, void *target, int val_index, + const type_identity *type, void *target, int val_index, bool exact, bool perr, bool signal) { if (signal) @@ -1100,7 +1100,7 @@ static bool doAssignDFObject(color_ostream *out, lua_State *state, } else if (!lua_isuserdata(state, val_index)) { - signal_typeid_error(out, state, type, "pointer to %s expected", + signal_typeid_error(out, state, type, "pointer to {} expected", val_index, perr, signal); return false; } @@ -1109,13 +1109,13 @@ static bool doAssignDFObject(color_ostream *out, lua_State *state, void *in_ptr = Lua::GetDFObject(state, type, val_index, exact); if (!in_ptr) { - signal_typeid_error(out, state, type, "incompatible pointer type: %s expected", + signal_typeid_error(out, state, type, "incompatible pointer type: {} expected", val_index, perr, signal); return false; } if (!type->copy(target, in_ptr)) { - signal_typeid_error(out, state, type, "no copy support for %s", + signal_typeid_error(out, state, type, "no copy support for {}", val_index, perr, signal); return false; } @@ -1124,13 +1124,13 @@ static bool doAssignDFObject(color_ostream *out, lua_State *state, } bool DFHack::Lua::AssignDFObject(color_ostream &out, lua_State *state, - type_identity *type, void *target, int val_index, + const type_identity *type, void *target, int val_index, bool exact_type, bool perr) { return doAssignDFObject(&out, state, type, target, val_index, exact_type, perr, false); } -void DFHack::Lua::CheckDFAssign(lua_State *state, type_identity *type, +void DFHack::Lua::CheckDFAssign(lua_State *state, const type_identity *type, void *target, int val_index, bool exact_type) { doAssignDFObject(NULL, state, type, target, val_index, exact_type, false, true); @@ -1281,29 +1281,30 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, DFHack: return (rv == LUA_OK); } -static bool init_interpreter(color_ostream &out, lua_State *state, const char* prompt, const char* hfile) +static bool init_interpreter(color_ostream &out, lua_State *state, std::string_view prompt, const std::filesystem::path& hfile) { lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN); lua_getfield(state, -1, "interpreter"); lua_remove(state, -2); - lua_pushstring(state, prompt); - lua_pushstring(state, hfile); + lua_pushlstring(state, prompt.data(), prompt.size()); + lua_pushlstring(state, hfile.string().data(), hfile.string().size()); return true; } bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, - const char *prompt, const char *hfile) + std::string_view prompt, std::filesystem::path hfile) { if (!out.is_console()) return false; - if (!hfile) - hfile = "dfhack-config/lua.history"; - if (!prompt) + if (hfile.empty()) + hfile = DFHack::Core::getInstance().getConfigPath() / "lua.history"; + if (prompt.empty()) prompt = "lua"; - using namespace std::placeholders; - auto init_fn = std::bind(init_interpreter, _1, _2, prompt, hfile); + auto init_fn = [&](color_ostream& out, lua_State* state) { + return init_interpreter(out, state, prompt, hfile); + }; return RunCoreQueryLoop(out, state, init_fn); } @@ -2134,7 +2135,7 @@ void DFHack::Lua::Core::Reset(color_ostream &out, const char *where) if (top != 0) { - out.printerr("Common lua context stack top left at %d after %s.\n", top, where); + out.printerr("Common lua context stack top left at {} after {}.\n", top, where); lua_settop(State, 0); } diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index cdacd5ebfe..0f990a6b84 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1253,7 +1253,7 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id field_error(state, UPVAL_METHOD_NAME, e.what(), "invoke"); } catch (std::exception &e) { - std::string tmp = stl_sprintf("C++ exception: %s", e.what()); + std::string tmp = fmt::format("C++ exception: {}", e.what()); field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); } diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index a0069fbbdc..9f0ecab738 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -132,7 +132,7 @@ bool LuaWrapper::LookupTypeInfo(lua_State *state, bool in_method) return true; } -void LuaWrapper::LookupInTable(lua_State *state, void *id, LuaToken *tname) +void LuaWrapper::LookupInTable(lua_State *state, const void *id, LuaToken *tname) { lua_rawgetp(state, LUA_REGISTRYINDEX, tname); lua_rawgetp(state, -1, id); @@ -221,7 +221,7 @@ void LuaWrapper::push_object_internal(lua_State *state, const type_identity *typ if (type->type() == IDTYPE_CLASS) { - virtual_identity *class_vid = virtual_identity::get(virtual_ptr(ptr)); + const virtual_identity *class_vid = virtual_identity::get(virtual_ptr(ptr)); if (class_vid) type = class_vid; } @@ -1009,7 +1009,8 @@ static int meta_type_tostring(lua_State *state) lua_getfield(state, -1, "__metatable"); const char *cname = lua_tostring(state, -1); - lua_pushstring(state, stl_sprintf("", cname).c_str()); + auto str = fmt::format("", cname); + lua_pushlstring(state, str.data(), str.size()); return 1; } @@ -1033,10 +1034,12 @@ static int meta_ptr_tostring(lua_State *state) lua_getfield(state, UPVAL_METATABLE, "__metatable"); const char *cname = lua_tostring(state, -1); - if (has_length) - lua_pushstring(state, stl_sprintf("<%s[%" PRIu64 "]: %p>", cname, length, (void*)ptr).c_str()); - else - lua_pushstring(state, stl_sprintf("<%s: %p>", cname, (void*)ptr).c_str()); + auto str = has_length ? + fmt::format("<{}[{}]: {}>", cname, length, static_cast(ptr)) : + fmt::format("<{}: {}>", cname, static_cast(ptr)); + + lua_pushlstring(state, str.data(), str.size()); + return 1; } diff --git a/library/MemoryPatcher.cpp b/library/MemoryPatcher.cpp new file mode 100644 index 0000000000..44aa865c42 --- /dev/null +++ b/library/MemoryPatcher.cpp @@ -0,0 +1,83 @@ +#include "Core.h" +#include "MemoryPatcher.h" + +namespace DFHack +{ + MemoryPatcher::MemoryPatcher(Process* p_) : p(p_) + { + if (!p) + p = Core::getInstance().p.get(); + } + + MemoryPatcher::~MemoryPatcher() + { + close(); + } + + bool MemoryPatcher::verifyAccess(void* target, size_t count, bool write) + { + auto* sptr = (uint8_t*)target; + uint8_t* eptr = sptr + count; + + // Find the valid memory ranges + if (ranges.empty()) + p->getMemRanges(ranges); + + // Find the ranges that this area spans + unsigned start = 0; + while (start < ranges.size() && ranges[start].end <= sptr) + start++; + if (start >= ranges.size() || ranges[start].start > sptr) + return false; + + unsigned end = start + 1; + while (end < ranges.size() && ranges[end].start < eptr) + { + if (ranges[end].start != ranges[end - 1].end) + return false; + end++; + } + if (ranges[end - 1].end < eptr) + return false; + + // Verify current permissions + for (unsigned i = start; i < end; i++) + if (!ranges[i].valid || !(ranges[i].read || ranges[i].execute) || ranges[i].shared) + return false; + + // Apply writable permissions & update + for (unsigned i = start; i < end; i++) + { + auto& perms = ranges[i]; + if ((perms.write || !write) && perms.read) + continue; + + save.push_back(perms); + perms.write = perms.read = true; + if (!p->setPermissions(perms, perms)) + return false; + } + + return true; + } + + bool MemoryPatcher::write(void* target, const void* src, size_t size) + { + if (!makeWritable(target, size)) + return false; + + memmove(target, src, size); + + p->flushCache(target, size); + return true; + } + + void MemoryPatcher::close() + { + for (size_t i = 0; i < save.size(); i++) + p->setPermissions(save[i], save[i]); + + save.clear(); + ranges.clear(); + }; +} diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 082657ea34..d91471803b 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -22,39 +22,49 @@ must not be misrepresented as being the original software. distribution. */ -#include "Internal.h" #include "Export.h" #include "MiscUtils.h" #include "ColorText.h" -#include "modules/DFSDL.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#ifndef LINUX_BUILD -// We don't want min and max macros +#ifdef WIN32 #define NOMINMAX - #include - // Suppress warning which occurs in header on some WinSDK versions - // See dfhack/dfhack#5147 for more information - #pragma warning(push) - #pragma warning(disable:4091) - #include - #pragma warning(pop) -#else - #include - #include - #include +#define WIN32_LEAN_AND_MEAN +#include +#include +// Suppress warning which occurs in header on some WinSDK versions +// See dfhack/dfhack#5147 for more information +#pragma warning(push) +#pragma warning(disable:4091) +#include +#pragma warning(pop) #endif -#include -#include -#include -#include -#include - -#include -#include -#include -#include +#ifdef LINUX_BUILD +#include +#include +#include +#endif NumberFormatType preferred_number_format_type = NumberFormatType::DEFAULT; @@ -499,8 +509,8 @@ uint64_t GetTimeMs64() /* Character decoding */ // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. -#define UTF8_ACCEPT 0 -#define UTF8_REJECT 12 +constexpr auto UTF8_ACCEPT = 0; +constexpr auto UTF8_REJECT = 12; static const uint8_t utf8d[] = { // The first part of the table maps bytes to character classes that @@ -653,20 +663,31 @@ std::string UTF2DF(const std::string &in) out.resize(pos); return out; } - -DFHACK_EXPORT std::string DF2CONSOLE(const std::string &in) +static bool console_is_utf8() { - bool is_utf = false; #ifdef LINUX_BUILD + static bool checked = false; + static bool is_utf = false; + if (checked) + return is_utf; + std::string locale = ""; if (getenv("LANG")) locale += getenv("LANG"); if (getenv("LC_CTYPE")) locale += getenv("LC_CTYPE"); locale = toUpper_cp437(locale); - is_utf = (locale.find("UTF-8") != std::string::npos) || - (locale.find("UTF8") != std::string::npos); + is_utf = (locale.find("UTF-8") != std::string::npos) || (locale.find("UTF8") != std::string::npos); + checked = true; + return is_utf; +#else + return true; // Since DF 53.11, Windows console is always UTF-8 #endif +} + +DFHACK_EXPORT std::string DF2CONSOLE(const std::string &in) +{ + bool is_utf = console_is_utf8(); return is_utf ? DF2UTF(in) : in; } diff --git a/library/PlugLoad.cpp b/library/PlugLoad.cpp index ec72e36fb9..d3b7560773 100644 --- a/library/PlugLoad.cpp +++ b/library/PlugLoad.cpp @@ -1,24 +1,18 @@ -#include "Core.h" #include "Debug.h" -#include "Export.h" #include "PluginManager.h" -#include "Hooks.h" #include -#include #include -#include - -#include -#include #ifdef WIN32 #define NOMINMAX +#define WIN32_LEAN_AND_MEAN #include +#include #define global_search_handle() GetModuleHandle(nullptr) #define get_function_address(plugin, function) GetProcAddress((HMODULE)plugin, function) #define clear_error() -#define load_library(fn) LoadLibraryW(fn.c_str()) +#define load_library(fn) LoadLibraryExW(fn.wstring().c_str(), NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); #define close_library(handle) (!(FreeLibrary((HMODULE)handle))) #else #include @@ -77,7 +71,7 @@ namespace DFHack DFLibrary* ret = (DFLibrary*)load_library(filename); if (!ret) { auto error = get_error(); - WARN(plugins).print("OpenPlugin on %s failed: %s\n", filename.string().c_str(), error.c_str()); + WARN(plugins).print("OpenPlugin on {} failed: {}\n", filename.string(), error); } return ret; } @@ -91,7 +85,7 @@ namespace DFHack if (res != 0) { auto error = get_error(); - WARN(plugins).print("ClosePlugin failed: %s\n", error.c_str()); + WARN(plugins).print("ClosePlugin failed: {}\n", error); } return (res == 0); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 575ebdc7f5..34095898db 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -22,36 +22,46 @@ must not be misrepresented as being the original software. distribution. */ -#include "modules/EventManager.h" -#include "modules/Filesystem.h" -#include "modules/Screen.h" -#include "modules/World.h" -#include "Internal.h" +#include "PluginManager.h" + +#include "ColorText.h" #include "Core.h" +#include "CoreDefs.h" +#include "Format.h" +#include "LuaWrapper.h" +#include "LuaTools.h" #include "MemAccess.h" -#include "PluginManager.h" +#include "MiscUtils.h" #include "RemoteServer.h" -#include "Console.h" #include "Types.h" #include "VersionInfo.h" -#include "DataDefs.h" -#include "MiscUtils.h" -#include "DFHackVersion.h" - -#include "LuaWrapper.h" -#include "LuaTools.h" - -using namespace DFHack; +#include "modules/EventManager.h" +#include "modules/Filesystem.h" +#include "modules/Screen.h" +#include "modules/World.h" +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -using std::string; +#include "df/viewscreen.h" + +#include +#include -#include +using namespace DFHack; +using std::string; #if defined(_LINUX) static const string plugin_suffix = ".plug.so"; @@ -238,7 +248,7 @@ bool Plugin::load(color_ostream &con) else if(state != PS_UNLOADED && state != PS_DELETED) { if (state == PS_BROKEN) - con.printerr("Plugin %s is broken - cannot be loaded\n", name.c_str()); + con.printerr("Plugin {} is broken - cannot be loaded\n", name); return false; } state = PS_LOADING; @@ -246,19 +256,19 @@ bool Plugin::load(color_ostream &con) // enter suspend CoreSuspender suspend; // open the library, etc - fprintf(stderr, "loading plugin %s\n", name.c_str()); + fmt::print(stderr, "loading plugin {}\n", name); DFLibrary * plug = OpenPlugin(path); if(!plug) { RefAutolock lock(access); if (!Filesystem::isfile(path)) { - con.printerr("Plugin %s does not exist on disk\n", name.c_str()); + con.printerr("Plugin {} does not exist on disk\n", name); state = PS_DELETED; return false; } else { - con.printerr("Can't load plugin %s\n", name.c_str()); + con.printerr("Can't load plugin {}\n", name); state = PS_UNLOADED; return false; } @@ -267,7 +277,7 @@ bool Plugin::load(color_ostream &con) #define plugin_check_symbol(sym) \ if (!LookupPlugin(plug, sym)) \ { \ - con.printerr("Plugin %s: missing symbol: %s\n", name.c_str(), sym); \ + con.printerr("Plugin {}: missing symbol: {}\n", name, sym); \ plugin_abort_load; \ return false; \ } @@ -281,7 +291,7 @@ bool Plugin::load(color_ostream &con) const char ** plug_name =(const char ** ) LookupPlugin(plug, "plugin_name"); if (name != *plug_name) { - con.printerr("Plugin %s: name mismatch, claims to be %s\n", name.c_str(), *plug_name); + con.printerr("Plugin {}: name mismatch, claims to be {}\n", name, *plug_name); plugin_abort_load; return false; } @@ -294,15 +304,15 @@ bool Plugin::load(color_ostream &con) const char *plug_git_desc = plug_git_desc_ptr ? *plug_git_desc_ptr : "unknown"; if (*plugin_abi_version != Version::dfhack_abi_version()) { - con.printerr("Plugin %s: ABI version mismatch (Plugin: %i, DFHack: %i)\n", + con.printerr("Plugin {}: ABI version mismatch (Plugin: {}, DFHack: {})\n", *plug_name, *plugin_abi_version, Version::dfhack_abi_version()); plugin_abort_load; return false; } if (strcmp(dfhack_version, *plug_version) != 0) { - con.printerr("Plugin %s was not built for this version of DFHack.\n" - "Plugin: %s, DFHack: %s\n", *plug_name, *plug_version, dfhack_version); + con.printerr("Plugin {} was not built for this version of DFHack.\n" + "Plugin: {}, DFHack: {}\n", *plug_name, *plug_version, dfhack_version); plugin_abort_load; return false; } @@ -310,18 +320,18 @@ bool Plugin::load(color_ostream &con) { if (strcmp(dfhack_git_desc, plug_git_desc) != 0) { - std::string msg = stl_sprintf("Warning: Plugin %s compiled for DFHack %s, running DFHack %s\n", + std::string msg = fmt::format("Warning: Plugin {} compiled for DFHack {}, running DFHack {}\n", *plug_name, plug_git_desc, dfhack_git_desc); con << msg << std::flush; std::cerr << msg << std::flush; } } else - con.printerr("Warning: Plugin %s missing git information\n", *plug_name); + con.printerr("Warning: Plugin {} missing git information\n", *plug_name); bool *plug_dev = (bool*)LookupPlugin(plug, "plugin_dev"); if (plug_dev && *plug_dev && getenv("DFHACK_NO_DEV_PLUGINS")) { - con.print("Skipping dev plugin: %s\n", *plug_name); + con.print("Skipping dev plugin: {}\n", *plug_name); plugin_abort_load; return false; } @@ -338,7 +348,7 @@ bool Plugin::load(color_ostream &con) } if (missing_globals.size()) { - con.printerr("Plugin %s is missing required globals: %s\n", + con.printerr("Plugin {} is missing required globals: {}\n", *plug_name, join_strings(", ", missing_globals).c_str()); plugin_abort_load; return false; @@ -364,18 +374,18 @@ bool Plugin::load(color_ostream &con) state = PS_LOADED; parent->registerCommands(this); if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled) - con.printerr("Plugin %s has no enabled var!\n", name.c_str()); + con.printerr("Plugin {} has no enabled var!\n", name); if (Core::getInstance().isWorldLoaded() && plugin_load_world_data && plugin_load_world_data(con) != CR_OK) - con.printerr("Plugin %s has failed to load saved world data.\n", name.c_str()); + con.printerr("Plugin {} has failed to load saved world data.\n", name); if (Core::getInstance().isMapLoaded() && plugin_load_site_data && World::IsSiteLoaded() && plugin_load_site_data(con) != CR_OK) - con.printerr("Plugin %s has failed to load saved site data.\n", name.c_str()); - fprintf(stderr, "loaded plugin %s; DFHack build %s\n", name.c_str(), plug_git_desc); - fflush(stderr); + con.printerr("Plugin {} has failed to load saved site data.\n", name); + + fmt::print(stderr, "loaded plugin {}; DFHack build {}\n", name, plug_git_desc); return true; } else { - con.printerr("Plugin %s has failed to initialize properly.\n", name.c_str()); + con.printerr("Plugin {} has failed to initialize properly.\n", name); plugin_is_enabled = 0; plugin_onupdate = 0; reset_lua(); @@ -393,7 +403,7 @@ bool Plugin::unload(color_ostream &con) { if (Screen::hasActiveScreens(this)) { - con.printerr("Cannot unload plugin %s: has active viewscreens\n", name.c_str()); + con.printerr("Cannot unload plugin {}: has active viewscreens\n", name); access->unlock(); return false; } @@ -402,7 +412,7 @@ bool Plugin::unload(color_ostream &con) if (plugin_onstatechange && plugin_onstatechange(con, SC_BEGIN_UNLOAD) != CR_OK) { - con.printerr("Plugin %s has refused to be unloaded.\n", name.c_str()); + con.printerr("Plugin {} has refused to be unloaded.\n", name); access->unlock(); return false; } @@ -418,9 +428,9 @@ bool Plugin::unload(color_ostream &con) CoreSuspender suspend; access->lock(); if (Core::getInstance().isMapLoaded() && plugin_save_site_data && World::IsSiteLoaded() && plugin_save_site_data(con) != CR_OK) - con.printerr("Plugin %s has failed to save site data.\n", name.c_str()); + con.printerr("Plugin {} has failed to save site data.\n", name); if (Core::getInstance().isWorldLoaded() && plugin_save_world_data && plugin_save_world_data(con) != CR_OK) - con.printerr("Plugin %s has failed to save world data.\n", name.c_str()); + con.printerr("Plugin {} has failed to save world data.\n", name); // holding the access lock while releasing the CoreSuspender creates a deadlock risk access->unlock(); } @@ -448,7 +458,7 @@ bool Plugin::unload(color_ostream &con) } else { - con.printerr("Plugin %s has failed to shutdown!\n",name.c_str()); + con.printerr("Plugin {} has failed to shutdown!\n",name); state = PS_BROKEN; } access->unlock(); @@ -460,7 +470,7 @@ bool Plugin::unload(color_ostream &con) return true; } else if (state == PS_BROKEN) - con.printerr("Plugin %s is broken - cannot be unloaded\n", name.c_str()); + con.printerr("Plugin {} is broken - cannot be unloaded\n", name); access->unlock(); return false; } @@ -490,7 +500,7 @@ command_result Plugin::invoke(color_ostream &out, const std::string & command, s else if (cmdIt->guard) { CoreSuspender suspend; if (!cmdIt->guard(Core::getInstance().getTopViewscreen())) { - out.printerr("Could not invoke %s: unsuitable UI state.\n", command.c_str()); + out.printerr("Could not invoke {}: unsuitable UI state.\n", command); cr = CR_WRONG_USAGE; } else { @@ -871,7 +881,7 @@ void PluginManager::init() loadAll(); bool any_loaded = false; - for (auto p : all_plugins) + for (auto& p : all_plugins) { if (p.second->getState() == Plugin::PS_LOADED) { @@ -894,13 +904,13 @@ bool PluginManager::addPlugin(string name) { if (all_plugins.find(name) != all_plugins.end()) { - Core::printerr("Plugin already exists: %s\n", name.c_str()); + Core::printerr("Plugin already exists: {}\n", name); return false; } std::filesystem::path path = getPluginPath(name); if (!Filesystem::isfile(path)) { - Core::printerr("Plugin does not exist: %s\n", name.c_str()); + Core::printerr("Plugin does not exist: {}\n", name); return false; } Plugin * p = new Plugin(core, path, name, this); @@ -944,7 +954,7 @@ bool PluginManager::load (const string &name) Plugin *p = (*this)[name]; if (!p) { - Core::printerr("Plugin failed to register: %s\n", name.c_str()); + Core::printerr("Plugin failed to register: {}\n", name); return false; } return p->load(core->getConsole()); @@ -969,7 +979,7 @@ bool PluginManager::unload (const string &name) std::lock_guard lock{*plugin_mutex}; if (!(*this)[name]) { - Core::printerr("Plugin does not exist: %s\n", name.c_str()); + Core::printerr("Plugin does not exist: {}\n", name); return false; } return (*this)[name]->unload(core->getConsole()); @@ -1069,8 +1079,8 @@ void PluginManager::registerCommands( Plugin * p ) std::string name = cmds[i].name; if (command_map.find(name) != command_map.end()) { - core->printerr("Plugin %s re-implements command \"%s\" (from plugin %s)\n", - p->getName().c_str(), name.c_str(), command_map[name]->getName().c_str()); + core->printerr("Plugin {} re-implements command \"{}\" (from plugin {})\n", + p->getName(), name, command_map[name]->getName()); continue; } command_map[name] = p; @@ -1096,12 +1106,12 @@ void PluginManager::doSaveData(color_ostream &out) if (World::IsSiteLoaded()) { cr = it->second->save_site_data(out); if (cr != CR_OK && cr != CR_NOT_IMPLEMENTED) - out.printerr("Plugin %s has failed to save site data.\n", it->first.c_str()); + out.printerr("Plugin {} has failed to save site data.\n", it->first); } cr = it->second->save_world_data(out); if (cr != CR_OK && cr != CR_NOT_IMPLEMENTED) - out.printerr("Plugin %s has failed to save world data.\n", it->first.c_str()); + out.printerr("Plugin {} has failed to save world data.\n", it->first); } } @@ -1112,7 +1122,7 @@ void PluginManager::doLoadWorldData(color_ostream &out) command_result cr = it->second->load_world_data(out); if (cr != CR_OK && cr != CR_NOT_IMPLEMENTED) - out.printerr("Plugin %s has failed to load saved world data.\n", it->first.c_str()); + out.printerr("Plugin {} has failed to load saved world data.\n", it->first); } } @@ -1123,7 +1133,7 @@ void PluginManager::doLoadSiteData(color_ostream &out) command_result cr = it->second->load_site_data(out); if (cr != CR_OK && cr != CR_NOT_IMPLEMENTED) - out.printerr("Plugin %s has failed to load saved site data.\n", it->first.c_str()); + out.printerr("Plugin {} has failed to load saved site data.\n", it->first); } } diff --git a/library/Process.cpp b/library/Process.cpp index 101eecbc71..1ff63622f0 100644 --- a/library/Process.cpp +++ b/library/Process.cpp @@ -22,20 +22,30 @@ must not be misrepresented as being the original software. distribution. */ -#ifndef WIN32 -#ifndef _DARWIN -#include -#endif /* ! _DARWIN */ -#endif /* ! WIN32 */ +#include "Format.h" +#include "MemAccess.h" +#include "Memory.h" +#include "MemoryPatcher.h" +#include "MiscUtils.h" +#include "VersionInfo.h" +#include "VersionInfoFactory.h" + +#include "modules/Filesystem.h" + +#include +#include +#include #include -#include +#include +#include +#include #include -#include +#include +#include #include #include -#include -#ifndef WIN32 +#ifdef LINUX_BUILD #include #include #include @@ -51,27 +61,24 @@ distribution. #include #include #endif /* _DARWIN */ -#endif /* ! WIN32 */ - -#include "Error.h" -#include "Internal.h" -#include "MemAccess.h" -#include "Memory.h" -#include "MiscUtils.h" -#include "VersionInfo.h" -#include "VersionInfoFactory.h" -#include "modules/Filesystem.h" -#ifndef WIN32 #include "md5wrapper.h" -#else /* WIN32 */ +#endif /* LINUX_BUILD */ + +#ifdef WIN32 #define _WIN32_WINNT 0x0600 #define WINVER 0x0600 + +#define NOMINMAX #define WIN32_LEAN_AND_MEAN #include #include +#include +#include +#include + +#include -#include #endif /* WIN32 */ using namespace DFHack; @@ -148,7 +155,7 @@ Process::Process(const VersionInfoFactory& known_versions) : identified(false) uint32_t pe_offset = readDWord(d->base + 0x3C); read(d->base + pe_offset, sizeof(d->pe_header), (uint8_t*)&(d->pe_header)); const size_t sectionsSize = sizeof(IMAGE_SECTION_HEADER) * d->pe_header.FileHeader.NumberOfSections; - d->sections = (IMAGE_SECTION_HEADER*)malloc(sectionsSize); + d->sections = (IMAGE_SECTION_HEADER*)std::malloc(sectionsSize); read(d->base + pe_offset + sizeof(d->pe_header), sectionsSize, (uint8_t*)(d->sections)); } catch (std::exception&) @@ -194,7 +201,7 @@ Process::Process(const VersionInfoFactory& known_versions) : identified(false) cerr << "1KB hexdump follows:" << endl; for(int i = 0; i < 64; i++) { - fprintf(stderr, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + fmt::print(std::cerr, "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}\n", first_kb[i*16], first_kb[i*16+1], first_kb[i*16+2], @@ -215,7 +222,7 @@ Process::Process(const VersionInfoFactory& known_versions) : identified(false) } free(wd); #else /* WIN32 */ - cerr << "PE timestamp: " << std::format("{:#0x}", my_pe) << endl; + cerr << "PE timestamp: " << fmt::format("{:#0x}", my_pe) << endl; #endif /* WIN32 */ } } @@ -233,6 +240,9 @@ Process::~Process() string Process::doReadClassName (void * vptr) { + if (!checkValidAddress(vptr)) + throw std::runtime_error(fmt::format("invalid vtable ptr {}", vptr)); + char* rtti = Process::readPtr(((char*)vptr - sizeof(void*))); #ifndef WIN32 char* typestring = Process::readPtr(rtti + sizeof(void*)); @@ -588,6 +598,20 @@ void Process::getMemRanges(vector& ranges) } #endif +bool Process::checkValidAddress(void* ptr) +{ + uintptr_t addr = reinterpret_cast(ptr); + auto validate = [&] (t_memrange& r) { + uintptr_t lo = reinterpret_cast(r.start); + uintptr_t hi = reinterpret_cast(r.end); + return addr >= lo && addr < hi; + }; + std::vector mr; + getMemRanges(mr); + bool valid = std::any_of(mr.begin(), mr.end(), validate); + return valid; +} + uintptr_t Process::getBase() { #if WIN32 @@ -644,7 +668,7 @@ uint32_t Process::getTickCount() #endif /* WIN32 */ } -std::filesystem::path Process::getPath() +[[deprecated]] std::filesystem::path Process::getPath() { #if defined(WIN32) || !defined(_DARWIN) return Filesystem::get_initial_cwd(); @@ -796,3 +820,10 @@ int Process::memProtect(void *ptr, const int length, const int prot) return !VirtualProtect(ptr, length, prot_native, &old_prot); #endif /* WIN32 */ } + +bool Process::patchMemory(void* target, const void* src, size_t count) +{ + MemoryPatcher patcher(this); + + return patcher.write(target, src, count); +} diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp index 20990428dc..c917a3232a 100644 --- a/library/RemoteClient.cpp +++ b/library/RemoteClient.cpp @@ -36,24 +36,25 @@ POSSIBILITY OF SUCH DAMAGE. */ -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include -#include #include -#include +#include +#include "ColorText.h" +#include "CoreDefs.h" #include "RemoteClient.h" -#include -#include "MiscUtils.h" -#include -#include -#include +#include "ActiveSocket.h" +#include "Host.h" +#include "SimpleSocket.h" -#include #include "json/json.h" @@ -180,7 +181,7 @@ bool RemoteClient::connect(int port) if (!socket->Open("localhost", port)) { - default_output().printerr("Could not connect to localhost:%d\n", port); + default_output().printerr("Could not connect to localhost:{}\n", port); return false; } @@ -335,8 +336,8 @@ bool RemoteFunctionBase::bind(color_ostream &out, RemoteClient *client, if (p_client == client && this->name == name && this->plugin == plugin) return true; - out.printerr("Function already bound to %s::%s\n", - this->plugin.c_str(), this->name.c_str()); + out.printerr("Function already bound to {}::{}\n", + this->plugin, this->name); return false; } @@ -372,15 +373,15 @@ command_result RemoteFunctionBase::execute(color_ostream &out, { if (!isValid()) { - out.printerr("Calling an unbound RPC function %s::%s.\n", - this->plugin.c_str(), this->name.c_str()); + out.printerr("Calling an unbound RPC function {}:{}.\n", + this->plugin, this->name); return CR_NOT_IMPLEMENTED; } if (!p_client->socket->IsSocketValid()) { - out.printerr("In call to %s::%s: invalid socket.\n", - this->plugin.c_str(), this->name.c_str()); + out.printerr("In call to {}:{}: invalid socket.\n", + this->plugin, this->name); return CR_LINK_FAILURE; } @@ -388,15 +389,15 @@ command_result RemoteFunctionBase::execute(color_ostream &out, if (send_size > RPCMessageHeader::MAX_MESSAGE_SIZE) { - out.printerr("In call to %s::%s: message too large: %d.\n", - this->plugin.c_str(), this->name.c_str(), send_size); + out.printerr("In call to {}:{}: message too large: {}.\n", + this->plugin, this->name, send_size); return CR_LINK_FAILURE; } if (!sendRemoteMessage(p_client->socket, id, input, true)) { - out.printerr("In call to %s::%s: I/O error in send.\n", - this->plugin.c_str(), this->name.c_str()); + out.printerr("In call to {}:{}: I/O error in send.\n", + this->plugin, this->name); return CR_LINK_FAILURE; } @@ -410,8 +411,8 @@ command_result RemoteFunctionBase::execute(color_ostream &out, if (!readFullBuffer(p_client->socket, &header, sizeof(header))) { - out.printerr("In call to %s::%s: I/O error in receive header.\n", - this->plugin.c_str(), this->name.c_str()); + out.printerr("In call to {}:{}: I/O error in receive header.\n", + this->plugin, this->name); return CR_LINK_FAILURE; } @@ -422,8 +423,8 @@ command_result RemoteFunctionBase::execute(color_ostream &out, if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE) { - out.printerr("In call to %s::%s: invalid received size %d.\n", - this->plugin.c_str(), this->name.c_str(), header.size); + out.printerr("In call to {}:{}: invalid received size {}.\n", + this->plugin, this->name, header.size); return CR_LINK_FAILURE; } @@ -431,8 +432,8 @@ command_result RemoteFunctionBase::execute(color_ostream &out, if (!readFullBuffer(p_client->socket, buf, header.size)) { - out.printerr("In call to %s::%s: I/O error in receive %d bytes of data.\n", - this->plugin.c_str(), this->name.c_str(), header.size); + out.printerr("In call to {}:{}: I/O error in receive {} bytes of data.\n", + this->plugin, this->name, header.size); return CR_LINK_FAILURE; } @@ -440,8 +441,8 @@ command_result RemoteFunctionBase::execute(color_ostream &out, case RPC_REPLY_RESULT: if (!output->ParseFromArray(buf, header.size)) { - out.printerr("In call to %s::%s: error parsing received result.\n", - this->plugin.c_str(), this->name.c_str()); + out.printerr("In call to {}:{}: error parsing received result.\n", + this->plugin, this->name); delete[] buf; return CR_LINK_FAILURE; } @@ -454,8 +455,8 @@ command_result RemoteFunctionBase::execute(color_ostream &out, if (text_data.ParseFromArray(buf, header.size)) text_decoder.decode(&text_data); else - out.printerr("In call to %s::%s: received invalid text data.\n", - this->plugin.c_str(), this->name.c_str()); + out.printerr("In call to {}:{}: received invalid text data.\n", + this->plugin, this->name); break; default: diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 4e26b06497..aa3deb201d 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -35,27 +35,36 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -#include -#include -#include +#ifdef WIN32 +#define NOMINMAX +#endif +#include +#include +#include +#include +#include +#include #include +#include #include -#include #include -#include +#include +#include + +#include "ColorText.h" +#include "Core.h" +#include "CoreDefs.h" +#include "Debug.h" +#include "MiscUtils.h" +#include "PluginManager.h" +#include "ActiveSocket.h" +#include "Host.h" +#include "RemoteClient.h" #include "RemoteServer.h" #include "RemoteTools.h" - #include "PassiveSocket.h" -#include "PluginManager.h" -#include "MiscUtils.h" -#include "Debug.h" - -#include -#include -#include +#include "SimpleSocket.h" #include #include @@ -189,14 +198,14 @@ ServerFunctionBase *ServerConnection::findFunction(color_ostream &out, const std Plugin *plug = Core::getInstance().plug_mgr->getPluginByName(plugin); if (!plug) { - out.printerr("No such plugin: %s\n", plugin.c_str()); + out.printerr("No such plugin: {}\n", plugin); return NULL; } svc = plug->rpc_connect(out); if (!svc) { - out.printerr("Plugin %s doesn't export any RPC methods.\n", plugin.c_str()); + out.printerr("Plugin {} doesn't export any RPC methods.\n", plugin); return NULL; } @@ -299,7 +308,7 @@ void ServerConnection::threadFn() if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE) { - out.printerr("In RPC server: invalid received size %d.\n", header.size); + out.printerr("In RPC server: invalid received size {}.\n", header.size); break; } @@ -307,7 +316,7 @@ void ServerConnection::threadFn() if (!readFullBuffer(socket, buf.get(), header.size)) { - out.printerr("In RPC server: I/O error in receive %d bytes of data.\n", header.size); + out.printerr("In RPC server: I/O error in receive {} bytes of data.\n", header.size); break; } @@ -323,17 +332,17 @@ void ServerConnection::threadFn() if (!fn) { - stream.printerr("RPC call of invalid id %d\n", header.id); + stream.printerr("RPC call of invalid id {}\n", header.id); } else { if (((fn->flags & SF_ALLOW_REMOTE) != SF_ALLOW_REMOTE) && strcmp(socket->GetClientAddr(), "127.0.0.1") != 0) { - stream.printerr("In call to %s: forbidden host: %s\n", fn->name, socket->GetClientAddr()); + stream.printerr("In call to {}: forbidden host: {}\n", fn->name, socket->GetClientAddr()); } else if (!fn->in()->ParseFromArray(buf.get(), header.size)) { - stream.printerr("In call to %s: could not decode input args.\n", fn->name); + stream.printerr("In call to {}: could not decode input args.\n", fn->name); } else { @@ -364,7 +373,7 @@ void ServerConnection::threadFn() if (out_size > RPCMessageHeader::MAX_MESSAGE_SIZE) { - stream.printerr("In call to %s: reply too large: %d.\n", + stream.printerr("In call to {}: reply too large: {}.\n", (fn ? fn->name : "UNKNOWN"), out_size); res = CR_LINK_FAILURE; } @@ -489,7 +498,7 @@ void ServerMainImpl::threadFn(std::promise promise, int port) break; case CSimpleSocket::SocketFirewallError: case CSimpleSocket::SocketProtocolError: - WARN(socket).print("Connection failed: %s\n", server.socket.DescribeError()); + WARN(socket).print("Connection failed: {}\n", server.socket.DescribeError()); break; default: break; diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 56b45eba01..5dab76e8cb 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -35,57 +35,58 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -#include -#include -#include -#include -#include -#include -#include -#include - #include "RemoteTools.h" -#include "PluginManager.h" + +#include "ColorText.h" +#include "Core.h" +#include "CoreDefs.h" +#include "DataDefs.h" +#include "DFHackVersion.h" +#include "LuaTools.h" #include "MiscUtils.h" +#include "PluginManager.h" #include "VersionInfo.h" -#include "DFHackVersion.h" #include "modules/Materials.h" #include "modules/Translation.h" #include "modules/Units.h" #include "modules/World.h" -#include "LuaTools.h" - -#include "DataDefs.h" -#include "df/plotinfost.h" #include "df/adventurest.h" -#include "df/world.h" -#include "df/world_data.h" -#include "df/unit.h" -#include "df/unit_misc_trait.h" -#include "df/unit_soul.h" -#include "df/unit_skill.h" +#include "df/creature_raw.h" +#include "df/global_objects.h" +#include "df/historical_entity.h" +#include "df/historical_figure.h" +#include "df/incident.h" +#include "df/inorganic_raw.h" +#include "df/language_name.h" #include "df/material.h" #include "df/matter_state.h" -#include "df/inorganic_raw.h" -#include "df/creature_raw.h" -#include "df/plant_raw.h" #include "df/nemesis_record.h" -#include "df/historical_figure.h" -#include "df/historical_entity.h" -#include "df/squad.h" +#include "df/plant_raw.h" +#include "df/plotinfost.h" +#include "df/profession.h" #include "df/squad_position.h" -#include "df/incident.h" +#include "df/squad.h" +#include "df/unit_misc_trait.h" +#include "df/unit_skill.h" +#include "df/unit_soul.h" +#include "df/unit.h" +#include "df/world_data.h" +#include "df/world.h" #include "BasicApi.pb.h" +#include #include #include -#include - +#include +#include +#include #include +#include +#include +#include using namespace DFHack; using namespace df::enums; @@ -696,16 +697,16 @@ command_result CoreService::BindMethod(color_ostream &stream, if (!fn) { - stream.printerr("RPC method not found: %s::%s\n", - in->plugin().c_str(), in->method().c_str()); + stream.printerr("RPC method not found: {}::{}\n", + in->plugin(), in->method()); return CR_FAILURE; } if (fn->p_in_template->GetTypeName() != in->input_msg() || fn->p_out_template->GetTypeName() != in->output_msg()) { - stream.printerr("Requested wrong signature for RPC method: %s::%s\n", - in->plugin().c_str(), in->method().c_str()); + stream.printerr("Requested wrong signature for RPC method: {}::{}\n", + in->plugin(), in->method()); return CR_FAILURE; } diff --git a/library/TileTypes.cpp b/library/TileTypes.cpp index 7636d5bab2..9f8a72ade6 100644 --- a/library/TileTypes.cpp +++ b/library/TileTypes.cpp @@ -61,9 +61,9 @@ static df::tiletype find_match( { if (warn) { - fprintf( - stderr, "NOTE: No shape %s in %s.\n", - enum_item_key(shape).c_str(), enum_item_key(mat).c_str() + fmt::print( + stderr, "NOTE: No shape {} in {}.\n", + enum_item_key(shape), enum_item_key(mat) ); } @@ -93,10 +93,10 @@ static df::tiletype find_match( { if (warn) { - fprintf( - stderr, "NOTE: No special %s in %s:%s.\n", - enum_item_key(special).c_str(), enum_item_key(mat).c_str(), - enum_item_key(shape).c_str() + fmt::print( + stderr, "NOTE: No special {} in {}:{}.\n", + enum_item_key(special), enum_item_key(mat), + enum_item_key(shape) ); } @@ -139,10 +139,9 @@ static df::tiletype find_match( { if (warn) { - fprintf( - stderr, "NOTE: No direction '%s' in %s:%s:%s.\n", - dir.c_str(), enum_item_key(mat).c_str(), - enum_item_key(shape).c_str(), enum_item_key(special).c_str() + fmt::print( + stderr, "NOTE: No direction '{}' in {}:{}:{}.\n", + dir, enum_item_key(mat), enum_item_key(shape), enum_item_key(special) ); } @@ -162,10 +161,10 @@ static df::tiletype find_match( { if (warn) { - fprintf( - stderr, "NOTE: No variant '%s' in %s:%s:%s:%s.\n", - enum_item_key(variant).c_str(), enum_item_key(mat).c_str(), - enum_item_key(shape).c_str(), enum_item_key(special).c_str(), dir.c_str() + fmt::print( + stderr, "NOTE: No variant '{}' in {}:{}:{}:{}.\n", + enum_item_key(variant), enum_item_key(mat), + enum_item_key(shape), enum_item_key(special), dir ); } @@ -214,8 +213,8 @@ static void init_tables() tile_to_mat[tiletype_material::STONE][tt] = ttm; if (ttm == tiletype::Void) - fprintf(stderr, "No match for tile %s in STONE.\n", - enum_item_key(tt).c_str()); + fmt::print(stderr, "No match for tile {} in STONE.\n", + enum_item_key(tt)); } else { @@ -233,8 +232,8 @@ static void init_tables() tile_to_mat[mat][tt] = ttm; if (ttm == tiletype::Void) - fprintf(stderr, "No match for tile %s in %s.\n", - enum_item_key(tt).c_str(), enum_item_key(mat).c_str()); + fmt::print(stderr, "No match for tile {} in {}.\n", + enum_item_key(tt), enum_item_key(mat)); } } } diff --git a/library/Types.cpp b/library/Types.cpp index 1dab657c1c..21437723f1 100644 --- a/library/Types.cpp +++ b/library/Types.cpp @@ -22,24 +22,20 @@ must not be misrepresented as being the original software. distribution. */ -#include "Internal.h" -#include "Export.h" #include "MiscUtils.h" -#include "Error.h" #include "Types.h" #include "modules/Filesystem.h" #include "df/general_ref.h" +#include "df/general_ref_type.h" +#include "df/global_objects.h" #include "df/specific_ref.h" +#include "df/specific_ref_type.h" -#include -#include - -#include -#include -#include #include +#include +#include int DFHack::getdir(std::filesystem::path dir, std::vector &files) diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 797161240e..2ade207cba 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -32,6 +32,7 @@ distribution. #include "Core.h" #include "DataFuncs.h" #include "MemAccess.h" +#include "MemoryPatcher.h" #include "VersionInfo.h" #include "VTableInterpose.h" @@ -210,7 +211,7 @@ void DFHack::addr_to_method_pointer_(void *pptr, void *addr) void *virtual_identity::get_vmethod_ptr(int idx) const { assert(idx >= 0); - void **vtable = (void**)vtable_ptr; + void **vtable = (void**)vtable_ptr(); if (!vtable) return NULL; return vtable[idx]; } @@ -218,7 +219,7 @@ void *virtual_identity::get_vmethod_ptr(int idx) const bool virtual_identity::set_vmethod_ptr(MemoryPatcher &patcher, int idx, void *ptr) const { assert(idx >= 0); - void **vtable = (void**)vtable_ptr; + void **vtable = (void**)vtable_ptr(); if (!vtable) return NULL; return patcher.write(&vtable[idx], &ptr, sizeof(void*)); } @@ -311,7 +312,7 @@ VMethodInterposeLinkBase::VMethodInterposeLinkBase(const virtual_identity *host, * - interpose_method comes from method_pointer_to_addr_ */ - fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %p (%s)\n", + fmt::print(stderr, "Bad VMethodInterposeLinkBase arguments: {} {} ({})\n", vmethod_idx, interpose_method, name_str); fflush(stderr); abort(); @@ -326,16 +327,13 @@ VMethodInterposeLinkBase::~VMethodInterposeLinkBase() VMethodInterposeLinkBase *VMethodInterposeLinkBase::get_first_interpose(const virtual_identity *id) { - auto pitem = id->interpose_list.find(vmethod_idx); - if (pitem == id->interpose_list.end()) - return NULL; - auto item = pitem->second; + auto item = id->get_interpose(vmethod_idx); if (!item) - return NULL; + return nullptr; if (item->host != id) - return NULL; + return nullptr; while (item->prev && item->prev->host == id) item = item->prev; @@ -364,7 +362,7 @@ bool VMethodInterposeLinkBase::find_child_hosts(const virtual_identity *cur, voi child_next.insert(base); found = true; } - else if (child->vtable_ptr) + else if (child->vtable_ptr()) { void *cptr = child->get_vmethod_ptr(vmethod_idx); if (cptr != vmptr) @@ -401,7 +399,7 @@ void VMethodInterposeLinkBase::on_host_delete(const virtual_identity *from) { // Otherwise, drop the link to that child: assert(child_hosts.count(from) != 0 && - from->interpose_list[vmethod_idx] == this); // while mutating this gets cleaned up below so machts nichts + from->get_interpose(vmethod_idx) == this); // while mutating this gets cleaned up below so machts nichts // Find and restore the original vmethod ptr auto last = this; @@ -413,7 +411,7 @@ void VMethodInterposeLinkBase::on_host_delete(const virtual_identity *from) // Unlink the chains child_hosts.erase(from); - from->interpose_list.erase(vmethod_idx); + from->set_interpose(vmethod_idx,nullptr); } } @@ -427,7 +425,7 @@ bool VMethodInterposeLinkBase::apply(bool enable) if (is_applied()) return true; - if (!host->vtable_ptr) + if (!host->vtable_ptr()) { std::cerr << "VMethodInterposeLinkBase::apply(" << enable << "): " << name() << ": no vtable pointer: " << host->getName() << endl; @@ -435,10 +433,10 @@ bool VMethodInterposeLinkBase::apply(bool enable) } // Retrieve the current vtable entry - auto l = host->interpose_list.find(vmethod_idx); + auto l = host->get_interpose(vmethod_idx); - VMethodInterposeLinkBase* old_link = (l != host->interpose_list.end()) ? (l->second) : nullptr; - VMethodInterposeLinkBase* next_link = NULL; + VMethodInterposeLinkBase* old_link = (l != nullptr) ? l : nullptr; + VMethodInterposeLinkBase* next_link = nullptr; while (old_link && old_link->host == host && old_link->priority > priority) { @@ -473,7 +471,7 @@ bool VMethodInterposeLinkBase::apply(bool enable) if (next_link) next_link->prev = this; else - host->interpose_list[vmethod_idx] = this; + host->set_interpose(vmethod_idx,this); child_hosts.clear(); child_next.clear(); @@ -532,9 +530,9 @@ bool VMethodInterposeLinkBase::apply(bool enable) for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it) { auto nhost = *it; - assert(nhost->interpose_list[vmethod_idx] == old_link); // acceptable due to assign below + assert(nhost->get_interpose(vmethod_idx) == old_link); // acceptable due to assign below nhost->set_vmethod_ptr(patcher, vmethod_idx, interpose_method); - nhost->interpose_list[vmethod_idx] = this; + nhost->set_interpose(vmethod_idx, this); } return true; @@ -573,7 +571,7 @@ void VMethodInterposeLinkBase::remove() MemoryPatcher patcher; // Remove from the list in the identity and vtable - host->interpose_list[vmethod_idx] = prev; + host->set_interpose(vmethod_idx, prev); host->set_vmethod_ptr(patcher, vmethod_idx, saved_chain); for (auto it = child_next.begin(); it != child_next.end(); ++it) @@ -589,8 +587,8 @@ void VMethodInterposeLinkBase::remove() for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it) { auto nhost = *it; - assert(nhost->interpose_list[vmethod_idx] == this); // acceptable due to assign below - nhost->interpose_list[vmethod_idx] = prev; + assert(nhost->get_interpose(vmethod_idx) == this); // acceptable due to assign below + nhost->set_interpose(vmethod_idx,prev); nhost->set_vmethod_ptr(patcher, vmethod_idx, saved_chain); if (prev) prev->child_hosts.insert(nhost); diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index 776362855a..94e2560e37 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -29,6 +29,7 @@ distribution. #include #include #include +#include #include "VersionInfoFactory.h" #include "VersionInfo.h" @@ -209,7 +210,7 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) else if (type == "md5-hash") { const char *cstr_value = pMemEntry->Attribute("value"); - fprintf(stderr, "%s (%s): MD5: %s\n", cstr_name, cstr_os, cstr_value ? cstr_value : "NULL"); + std::cerr << fmt::format("{} ({}): MD5: {}\n", cstr_name, cstr_os, cstr_value ? cstr_value : "NULL"); if(!cstr_value) throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name); mem->addMD5(cstr_value); @@ -217,7 +218,7 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) else if (type == "binary-timestamp") { const char *cstr_value = pMemEntry->Attribute("value"); - fprintf(stderr, "%s (%s): PE: %s\n", cstr_name, cstr_os, cstr_value ? cstr_value : "NULL"); + std::cerr << fmt::format("{} ({}): PE: {}\n", cstr_name, cstr_os, cstr_value ? cstr_value : "NULL"); if(!cstr_value) throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name); mem->addPE(strtol(cstr_value, 0, 16)); @@ -226,9 +227,9 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) } // method // load the XML file with offsets -bool VersionInfoFactory::loadFile(string path_to_xml) +bool VersionInfoFactory::loadFile(std::filesystem::path path_to_xml) { - TiXmlDocument doc( path_to_xml.c_str() ); + TiXmlDocument doc( path_to_xml.string().c_str() ); std::cerr << "Loading " << path_to_xml << " ... "; //bool loadOkay = doc.LoadFile(); if (!doc.LoadFile()) diff --git a/library/dfhack-run.cpp b/library/dfhack-run.cpp index df10bf1485..171032a39c 100644 --- a/library/dfhack-run.cpp +++ b/library/dfhack-run.cpp @@ -119,8 +119,8 @@ int main (int argc, char *argv[]) if (rv == CR_OK) { for (int i = 0; i < run_call.out()->value_size(); i++) - printf("%s%s", (i>0?"\t":""), run_call.out()->value(i).c_str()); - printf("\n"); + fmt::print("{}{}", (i>0?"\t":""), run_call.out()->value(i)); + fmt::print("\n"); } } else diff --git a/library/include/BitArray.h b/library/include/BitArray.h index bad77d7935..7658e33102 100644 --- a/library/include/BitArray.h +++ b/library/include/BitArray.h @@ -23,67 +23,105 @@ distribution. */ #pragma once -#include "Export.h" #include "Error.h" -#include -#include -#include -#include -#include -#include + +#include +#include +#include #include +#include +#include +#include + namespace DFHack { template class BitArray { + private: + // note that these are mandated by the implementation of flagarrayst in DF code, and must be exactly as below + using buffer_type = unsigned char; + using size_type = int32_t; + + buffer_type* _bits; + size_type _size; + + void resize(size_type newsize, const BitArray* replacement) + { + if (newsize == _size) + return; + + if (newsize == 0) + { + delete[] _bits; + _bits = nullptr; + _size = 0; + return; + } + + buffer_type* old_data = _bits; + + _bits = new buffer_type[newsize]; + + buffer_type* copysrc = replacement ? replacement->_bits : old_data; + size_type copysize = replacement ? replacement->_size : _size; + + if (copysrc) + std::memcpy(_bits, copysrc, std::min(copysize, newsize)); + + if (newsize > _size) + std::memset(_bits + _size, 0, newsize - _size); + + delete[] old_data; + + _size = newsize; + } + + void extend(T index) + { + size_type newsize = (index + 7 ) / 8; + if (newsize > _size) + resize(newsize); + } + public: - BitArray() : bits(NULL), size(0) {} - BitArray(const BitArray &other) : bits(NULL), size(0) + BitArray() : _bits(nullptr), _size(0) {} + BitArray(const BitArray &other) : _bits(nullptr), _size(0) { - *this = other; + resize(other._size, &other); } ~BitArray() { - free(bits); + delete [] _bits; } - explicit BitArray(T last) : bits(NULL), size(0) { + explicit BitArray(T last) : _bits(nullptr), _size(0) { extend(last); } - explicit BitArray(unsigned bytes) : bits(NULL), size(0) { + explicit BitArray(unsigned bytes) : _bits(nullptr), _size(0) { resize(bytes); } - void clear_all ( void ) + size_type size() const { return _size; } + buffer_type* bits() const { return _bits; } + + void resize( size_type newsize ) { - if(bits) - memset(bits, 0, size); + resize(newsize, nullptr); } - void resize (unsigned newsize) + + void clear_all ( void ) { - if (newsize == size) - return; - uint8_t* mem = (uint8_t *) realloc(bits, newsize); - if(!mem && newsize != 0) - throw std::bad_alloc(); - bits = mem; - if (newsize > size) - memset(bits+size, 0, newsize-size); - size = newsize; - } - BitArray &operator= (const BitArray &other) - { - resize(other.size); - memcpy(bits, other.bits, size); - return *this; + if(_bits) + memset(_bits, 0, _size); } - void extend (T index) + + BitArray& operator= (const BitArray& other) { - unsigned newsize = (index / 8) + 1; - if (newsize > size) - resize(newsize); + resize(other._size, &other); + return *this; } + void set (T index, bool value = true) { if(!value) @@ -91,71 +129,74 @@ namespace DFHack clear(index); return; } - uint32_t byte = index / 8; + size_type byte = index / 8; extend(index); - //if(byte < size) - { - uint8_t bit = 1 << (index % 8); - bits[byte] |= bit; - } + uint8_t bit = 1 << (index % 8); + _bits[byte] |= bit; } + void clear (T index) { - uint32_t byte = index / 8; - if(byte < size) + size_type byte = index / 8; + if(byte < _size) { uint8_t bit = 1 << (index % 8); - bits[byte] &= ~bit; + _bits[byte] &= ~bit; } } + void toggle (T index) { - uint32_t byte = index / 8; + size_type byte = index / 8; extend(index); - //if(byte < size) - { - uint8_t bit = 1 << (index % 8); - bits[byte] ^= bit; - } + uint8_t bit = 1 << (index % 8); + _bits[byte] ^= bit; } + bool is_set (T index) const { - uint32_t byte = index / 8; - if(byte < size) + size_type byte = index / 8; + if(byte < _size) { uint8_t bit = 1 << (index % 8); - return bit & bits[byte]; + return bit & _bits[byte]; } else return false; } + /// WARNING: this can truncate long bit arrays - uint32_t as_int () + template + I as_int () const { - if(!bits) + if(!_bits) return 0; - if(size >= 4) - return *(uint32_t *)bits; - uint32_t target = 0; - memcpy (&target, bits,size); + if (_size >= sizeof(I)) + // FIXME (C++23): should be std::start_lifetime_as + return *reinterpret_cast(_bits); + I target = 0; + std::memcpy(&target, _bits, _size); return target; } + /// WARNING: this can be truncated / only overwrite part of the data - bool operator =(uint32_t data) + template + bool operator =(I data) { - if(!bits) + if(!_bits) return false; - if (size >= 4) + if (_size >= sizeof(I)) { - (*(uint32_t *)bits) = data; + *reinterpret_cast(_bits) = data; return true; } - memcpy(bits, &data, size); + std::memcpy(_bits, &data, _size); return true; } + friend std::ostream& operator<< (std::ostream &out, BitArray &ba) { std::stringstream sstr; - for (int i = 0; i < ba.size * 8; i++) + for (int i = 0; i < ba._size * 8; i++) { if(ba.is_set((T)i)) sstr << "1 "; @@ -165,23 +206,44 @@ namespace DFHack out << sstr.str(); return out; } - uint8_t * bits; - uint32_t size; }; template class DfArray { + private: T *m_data; unsigned short m_size; + + void resize(unsigned short new_size, const DfArray* replacement) + { + if (new_size == m_size) + return; + + T* old_data = m_data; + + m_data = (T*) new T[new_size]; + + T* copysrc = replacement ? replacement->m_data : old_data; + unsigned short copysize = replacement ? replacement->m_size : m_size; + + if (copysrc) + std::memcpy(m_data, copysrc, sizeof(T) * std::min(copysize, new_size)); + + if (new_size > m_size) + std::memset(m_data + m_size, 0, sizeof(T) * (new_size - m_size)); + + delete[] old_data; + + m_size = new_size; + } public: - DfArray() : m_data(NULL), m_size(0) {} - ~DfArray() { free(m_data); } + DfArray() : m_data(nullptr), m_size(0) {} + ~DfArray() { delete[] m_data; } - DfArray(const DfArray &other) : m_data(NULL), m_size(0) + DfArray(const DfArray &other) : m_data(nullptr), m_size(0) { - resize(other.m_size); - memcpy(m_data, other.m_data,m_size*sizeof(T)); + resize(other.m_size, &other); } typedef T value_type; @@ -198,28 +260,12 @@ namespace DFHack void resize(unsigned new_size) { - if (new_size == m_size) - return; - if(!m_data) - { - m_data = (T*) malloc(sizeof(T)*new_size); - } - else - { - T* mem = (T*) realloc(m_data, sizeof(T)*new_size); - if(!mem && new_size != 0) - throw std::bad_alloc(); - m_data = mem; - } - if (new_size > m_size) - memset(m_data+sizeof(T)*m_size, 0, sizeof(T)*(new_size - m_size)); - m_size = new_size; + resize(new_size, nullptr); } DfArray &operator= (const DfArray &other) { - resize(other.size()); - memcpy(data(), other.data(), sizeof(T)*size()); + resize(other.size(), &other); return *this; } diff --git a/library/include/ColorText.h b/library/include/ColorText.h index f5768c0ffa..98bba1447e 100644 --- a/library/include/ColorText.h +++ b/library/include/ColorText.h @@ -24,6 +24,7 @@ distribution. #pragma once #include "Export.h" +#include "Format.h" #include #include @@ -104,13 +105,24 @@ namespace DFHack color_ostream(); virtual ~color_ostream(); - /// Print a formatted string, like printf - void print(const char *format, ...) Wformat(printf,2,3); - void vprint(const char *format, va_list args) Wformat(printf,2,0); + template + void print(fmt::format_string format, Args&& ... args) + { + auto str = fmt::format(format, std::forward(args)...); + flush_buffer(false); + add_text(cur_color, str); + } - /// Print a formatted string, like printf, in red - void printerr(const char *format, ...) Wformat(printf,2,3); - void vprinterr(const char *format, va_list args) Wformat(printf,2,0); + template + void printerr(fmt::format_string format, Args&& ... args) + { + auto str = fmt::format(format, std::forward(args)...); + if (log_errors_to_stderr) { + std::cerr << str; + } + flush_buffer(false); + add_text(COLOR_LIGHTRED, str); + } /// Get color color_value color() { return cur_color; } @@ -122,6 +134,9 @@ namespace DFHack virtual bool is_console() { return false; } virtual color_ostream *proxy_target() { return NULL; } + virtual bool can_clear() const { return false; } + virtual void clear() {} + static bool log_errors_to_stderr; }; @@ -175,4 +190,5 @@ namespace DFHack void decode(dfproto::CoreTextNotification *data); }; + } diff --git a/library/include/Commands.h b/library/include/Commands.h new file mode 100644 index 0000000000..163d2e164f --- /dev/null +++ b/library/include/Commands.h @@ -0,0 +1,30 @@ +#pragma once + +#include "ColorText.h" +#include "CoreDefs.h" +#include "Core.h" + +#include +#include + +namespace DFHack +{ + namespace Commands + { + command_result help(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result load(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result enable(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result plug(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result type(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result keybinding(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result alias(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result fpause(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result clear(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result kill_lua(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result script(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result show(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result hide(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result sc_script(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + command_result dump_rpc(color_ostream& con, Core& core, const std::string& first, const std::vector& parts); + } +} diff --git a/library/include/Console.h b/library/include/Console.h index 4397c75954..cbf7d58511 100644 --- a/library/include/Console.h +++ b/library/include/Console.h @@ -141,8 +141,9 @@ namespace DFHack /// shutdown the console. NOT thread-safe bool shutdown( void ); + bool can_clear() const { return true; } /// Clear the console, along with its scrollback - void clear(); + void clear() override; /// Position cursor at x,y. 1,1 = top left corner void gotoxy(int x, int y); /// Enable or disable the caret/cursor diff --git a/library/include/Core.h b/library/include/Core.h index 383a0a9c95..2957a1347a 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -29,15 +29,15 @@ distribution. #include "Export.h" #include "Hooks.h" -#include "modules/Graphic.h" +#include "modules/Filesystem.h" +#include #include #include #include #include #include #include -#include #include #include #include @@ -59,16 +59,17 @@ namespace DFHack constexpr auto DFH_MOD_SHIFT = 1; constexpr auto DFH_MOD_CTRL = 2; constexpr auto DFH_MOD_ALT = 4; + constexpr auto DFH_MOD_SUPER = 8; class Process; class Module; - class Materials; struct VersionInfo; class VersionInfoFactory; class PluginManager; class Core; class ServerMain; class CoreSuspender; + class HotkeyManager; namespace Lua { namespace Core { DFHACK_EXPORT void Reset(color_ostream &out, const char *where); @@ -152,26 +153,22 @@ namespace DFHack friend void ::dfhooks_update(); friend void ::dfhooks_prerender(); friend bool ::dfhooks_sdl_event(SDL_Event* event); + friend void ::dfhooks_sdl_loop(); friend bool ::dfhooks_ncurses_key(int key); public: - /// Get the single Core instance or make one. + /// Get the current active Core instance. will assert if none exists + /// Use noInstance() to check first if unsure static Core& getInstance(); + static bool noInstance() { return active_instance == nullptr; } + /// check if the activity lock is owned by this thread bool isSuspended(void); /// Is everything OK? bool isValid(void) { return !errorstate; } - /// get the materials module - Materials * getMaterials(); - /// get the graphic module - Graphic * getGraphic(); - /// sets the current hotkey command - bool setHotkeyCmd( std::string cmd ); - /// removes the hotkey command and gives it to the caller thread - std::string getHotkeyCmd( bool &keep_going ); - command_result runCommand(color_ostream &out, const std::string &command, std::vector ¶meters, bool no_autocomplete = false); - command_result runCommand(color_ostream &out, const std::string &command); + command_result runCommand(color_ostream& out, const std::string& command); + bool loadScriptFile(color_ostream &out, std::filesystem::path fname, bool silent = false); bool addScriptPath(std::filesystem::path path, bool search_before = false); @@ -180,14 +177,13 @@ namespace DFHack std::filesystem::path findScript(std::string name); void getScriptPaths(std::vector *dest); - bool getSuppressDuplicateKeyboardEvents(); + bool getSuppressDuplicateKeyboardEvents() const; void setSuppressDuplicateKeyboardEvents(bool suppress); void setMortalMode(bool value); + bool getMortalMode(); void setArmokTools(const std::vector &tool_names); + bool isArmokTool(const std::string& name); - bool ClearKeyBindings(std::string keyspec); - bool AddKeyBinding(std::string keyspec, std::string cmdline); - std::vector ListKeyBindings(std::string keyspec); int8_t getModstate() { return modstate; } bool AddAlias(const std::string &name, const std::vector &command, bool replace = false); @@ -198,10 +194,12 @@ namespace DFHack std::map> ListAliases(); std::string GetAliasCommand(const std::string &name, bool ignore_params = false); + // note that this isn't valid until after DFHack is initialized by DF calling `dfhooks_init` + // that means that it's invalid during at-init static initialization std::filesystem::path getHackPath(); - bool isWorldLoaded() { return (last_world_data_ptr != NULL); } - bool isMapLoaded() { return (last_local_map_ptr != NULL && last_world_data_ptr != NULL); } + bool isWorldLoaded() { return (last_world_data_ptr != nullptr); } + bool isMapLoaded() { return (last_local_map_ptr != nullptr && last_world_data_ptr != nullptr); } static df::viewscreen *getTopViewscreen(); @@ -210,10 +208,22 @@ namespace DFHack std::unique_ptr p; std::shared_ptr vinfo; - static void print(const char *format, ...) Wformat(printf,1,2); - static void printerr(const char *format, ...) Wformat(printf,1,2); + template + static void print(fmt::format_string format, Args&& ... args) + { + color_ostream_proxy proxy(getInstance().con); + proxy.print(format, std::forward(args)...); + } + + template + static void printerr(fmt::format_string format, Args&& ... args) + { + color_ostream_proxy proxy(getInstance().con); + proxy.printerr(format, std::forward(args)...); + } - PluginManager *getPluginManager() { return plug_mgr; } + PluginManager* getPluginManager() const { return plug_mgr; } + HotkeyManager* getHotkeyManager() { return hotkey_mgr; } static void cheap_tokenise(std::string const& input, std::vector &output); @@ -225,20 +235,59 @@ namespace DFHack return State; } - private: - DFHack::Console con; + static command_result enableLuaScript(color_ostream& out, const std::string_view name, bool enabled); + + const std::vector getStateChangeScripts() const + { + return state_change_scripts; + } + + void addStateChangeScript(const StateChangeScript& script) + { + state_change_scripts.push_back(script); + } + + bool removeStateChangeScript(const StateChangeScript& script) + { + auto it = std::find(state_change_scripts.begin(), state_change_scripts.end(), script); + if (it != state_change_scripts.end()) + { + state_change_scripts.erase(it); + return true; + } + return false; + } + + // Note that this path should be treated as potentially changeable over the life of a Core instance + // Consumers should not cache this path in long-lived local variables + const std::filesystem::path getConfigPath() + { + return Filesystem::getInstallDir() / "dfhack-config"; + } + + const std::filesystem::path getConfigDefaultsPath() + { + return getHackPath() / "data" / "dfhack-config-defaults"; + } Core(); ~Core(); + private: + static Core* active_instance; + + DFHack::Console con; + + struct Private; std::unique_ptr d; - bool InitMainThread(); + bool InitMainThread(std::filesystem::path path); bool InitSimulationThread(); int Update (void); int Shutdown (void); bool DFH_SDL_Event(SDL_Event* event); + void DFH_SDL_Loop(); bool ncurses_wgetch(int in, int & out); bool DFH_ncurses_key(int key); @@ -248,11 +297,13 @@ namespace DFHack void onStateChange(color_ostream &out, state_change_event event); void handleLoadAndUnloadScripts(color_ostream &out, state_change_event event); - Core(Core const&); // Don't Implement - void operator=(Core const&); // Don't implement + bool loadScriptPaths(color_ostream& out, bool silent = false); + + Core(Core const&) = delete; + void operator=(Core const&) = delete; // report error to user while failing - void fatal (std::string output, const char * title = NULL); + void fatal (std::string output, const char * title = nullptr); // 1 = fatal failure bool errorstate; @@ -261,46 +312,24 @@ namespace DFHack // FIXME: shouldn't be kept around like this std::unique_ptr vif; - // Module storage - struct - { - Materials * pMaterials; - Graphic * pGraphic; - } s_mods; - std::vector> allModules; - DFHack::PluginManager * plug_mgr; + DFHack::PluginManager *plug_mgr; + + // Hotkey Manager + DFHack::HotkeyManager *hotkey_mgr; std::vector script_paths[3]; std::mutex script_path_mutex; - // hotkey-related stuff - struct KeyBinding { - int modifiers; - std::vector command; - std::string cmdline; - std::string focus; - }; int8_t modstate; bool suppress_duplicate_keyboard_events; - bool mortal_mode; + std::atomic mortal_mode; std::unordered_set armok_tools; - std::map > key_bindings; - std::string hotkey_cmd; - enum hotkey_set_t { - NO, - SET, - SHUTDOWN, - }; - hotkey_set_t hotkey_set; - std::mutex HotkeyMutex; - std::condition_variable HotkeyCond; + std::mutex armok_mutex; std::map> aliases; std::recursive_mutex alias_mutex; - bool SelectHotkey(int key, int modifiers); - // for state change tracking df::world_data *last_world_data_ptr; // for state change tracking @@ -333,6 +362,8 @@ namespace DFHack uint32_t unpaused_ms; // reset to 0 on map load + std::filesystem::path hack_path; + friend class CoreService; friend class ServerConnection; friend class CoreSuspender; @@ -503,4 +534,12 @@ namespace DFHack operator bool() const { return owns_lock(); } }; + // unclassified functions related to core + + void help_helper(color_ostream& con, const std::string& entry_name); + std::string dfhack_version_desc(); + bool is_builtin(color_ostream& con, const std::string& command); + std::string sc_event_name(state_change_event id); + state_change_event sc_event_id(std::string name); + } diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index e8e54fac41..37892391a4 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -24,16 +24,19 @@ distribution. #pragma once +#include +#include #include -#include #include +#include #include -#include #include +#include #include #include "BitArray.h" #include "Export.h" +#include "Format.h" struct lua_State; @@ -71,7 +74,7 @@ namespace DFHack PTRFLAG_HAS_BAD_POINTERS = 2, }; - typedef void *(*TAllocateFn)(void*,const void*); + using TAllocateFn = void *(*)(void*, const void*); class DFHACK_EXPORT type_identity { const size_t size; @@ -121,10 +124,10 @@ namespace DFHack constructed_identity(size_t size, const TAllocateFn alloc) : type_identity(size), allocator(alloc) {}; - virtual bool can_allocate() const { return (allocator != NULL); } - virtual void *do_allocate() const { return allocator(NULL,NULL); } + virtual bool can_allocate() const { return (allocator != nullptr); } + virtual void *do_allocate() const { return allocator(nullptr,nullptr); } virtual bool do_copy(void *tgt, const void *src) const { return allocator(tgt,src) == tgt; } - virtual bool do_destroy(void *obj) const { return allocator(NULL,obj) == obj; } + virtual bool do_destroy(void *obj) const { return allocator(nullptr,obj) == obj; } public: virtual bool isPrimitive() const { return false; } virtual bool isConstructed() const { return true; } @@ -134,28 +137,30 @@ namespace DFHack }; class DFHACK_EXPORT compound_identity : public constructed_identity { - static compound_identity *list; - mutable compound_identity *next; + static std::list* list; + static std::unordered_map* parent_map; + static std::unordered_map>* children_map; + static std::vector* top_scope; const char *dfhack_name; - mutable compound_identity *scope_parent; - mutable std::vector scope_children; - static std::vector top_scope; + const compound_identity *const scope_parent; + + static void ensure_compound_identity_init(); protected: compound_identity(size_t size, TAllocateFn alloc, const compound_identity *scope_parent, const char *dfhack_name); - virtual void doInit(Core *core); + virtual void doInit(Core *core) const; public: const char *getName() const { return dfhack_name; } virtual const std::string getFullName() const; - const compound_identity *getScopeParent() const { return scope_parent; } - const std::vector &getScopeChildren() const { return scope_children; } - static const std::vector &getTopScope() { return top_scope; } + const compound_identity *getScopeParent() const { return (*parent_map)[this]; } + const std::vector &getScopeChildren() const { return (*children_map)[this]; } + static const std::vector &getTopScope() { return *top_scope; } static void Init(Core *core); }; @@ -201,7 +206,7 @@ namespace DFHack class DFHACK_EXPORT enum_identity : public compound_identity { public: struct ComplexData { - std::map value_index_map; + std::unordered_map value_index_map; std::vector index_value_map; ComplexData(std::initializer_list values); size_t size() const { @@ -257,8 +262,8 @@ namespace DFHack }; struct struct_field_info_extra { - enum_identity *index_enum; - type_identity *ref_target; + const enum_identity *index_enum; + const type_identity *ref_target; const char *union_tag_field; const char *union_tag_attr; const char *original_name; @@ -286,14 +291,15 @@ namespace DFHack }; class DFHACK_EXPORT struct_identity : public compound_identity { - mutable struct_identity *parent; - mutable std::vector children; - bool has_children; + static std::unordered_map* parent_map; + static std::unordered_map>* children_map; const struct_field_info *fields; + static void ensure_struct_identity_init(); + protected: - virtual void doInit(Core *core); + virtual void doInit(Core *core) const override; public: struct_identity(size_t size, TAllocateFn alloc, @@ -302,9 +308,9 @@ namespace DFHack virtual identity_type type() const { return IDTYPE_STRUCT; } - const struct_identity *getParent() const { return parent; } - const std::vector &getChildren() const { return children; } - bool hasChildren() const { return has_children; } + const struct_identity *getParent() const { return (*parent_map)[this]; } + const std::vector &getChildren() const { return (*children_map)[this]; } + bool hasChildren() const { return (*children_map)[this].size() > 0; } const struct_field_info *getFields() const { return fields; } @@ -326,8 +332,8 @@ namespace DFHack class DFHACK_EXPORT union_identity : public struct_identity { public: union_identity(size_t size, TAllocateFn alloc, - compound_identity *scope_parent, const char *dfhack_name, - struct_identity *parent, const struct_field_info *fields); + const compound_identity *scope_parent, const char *dfhack_name, + const struct_identity *parent, const struct_field_info *fields); virtual identity_type type() const { return IDTYPE_UNION; } @@ -351,6 +357,30 @@ namespace DFHack virtual void build_metatable(lua_State *state) const; }; + namespace + { + template + struct overload : Bases ... + { + using is_transparent = void; + using Bases::operator() ...; + }; + + struct char_pointer_hash + { + auto operator()(const char* ptr) const noexcept + { + return std::hash{}(ptr); + } + }; + + using transparent_string_hash = overload< + std::hash, + std::hash, + char_pointer_hash + >; + } + #ifdef _MSC_VER using virtual_ptr = void*; #else @@ -361,27 +391,55 @@ namespace DFHack class MemoryPatcher; class DFHACK_EXPORT virtual_identity : public struct_identity { - static std::map known; + public: + using interpose_t = VMethodInterposeLinkBase*; + using interpose_list_t = std::unordered_map; - const char *original_name; + private: + static std::unordered_map> *name_lookup; + static std::unordered_map* known; + static std::unordered_map* vtable_ptr_map; + static std::unordered_map* interpose_list_map; - mutable void *vtable_ptr; + const char *original_name; bool is_plugin; friend class VMethodInterposeLinkBase; - mutable std::map interpose_list; + + static void ensure_virtual_identity_init(); protected: - virtual void doInit(Core *core); + virtual void doInit(Core *core) const override; static void *get_vtable(virtual_ptr instance_ptr) { return *(void**)instance_ptr; } - bool can_allocate() const { return struct_identity::can_allocate() && (vtable_ptr != NULL); } + void* vtable_ptr() const + { + auto& lst = (*vtable_ptr_map); + auto it = lst.find(this); + return it != lst.end() ? it->second : nullptr; + } + + bool can_allocate() const { return struct_identity::can_allocate() && (vtable_ptr() != nullptr); } void *get_vmethod_ptr(int index) const; bool set_vmethod_ptr(MemoryPatcher &patcher, int index, void *ptr) const; + interpose_list_t& get_interpose_list() const { return (*interpose_list_map)[this]; } + interpose_t get_interpose(int index) const { + auto &lst = get_interpose_list(); + auto it = lst.find(index); + return it != lst.end() ? it->second : nullptr; + } + void set_interpose(int index, interpose_t link) const { + auto &lst = get_interpose_list(); + if (link) + lst[index] = link; + else + lst.erase(index); + } + public: virtual_identity(size_t size, const TAllocateFn alloc, const char *dfhack_name, const char *original_name, @@ -394,16 +452,16 @@ namespace DFHack const char *getOriginalName() const { return original_name ? original_name : getName(); } public: - static virtual_identity *get(virtual_ptr instance_ptr); + static const virtual_identity *get(virtual_ptr instance_ptr); - static virtual_identity *find(void *vtable); - static virtual_identity *find(const std::string &name); + static const virtual_identity *find(void *vtable); + static const virtual_identity *find(std::string_view name); bool is_instance(virtual_ptr instance_ptr) const { if (!instance_ptr) return false; - if (vtable_ptr) { + if (vtable_ptr()) { void *vtable = get_vtable(instance_ptr); - if (vtable == vtable_ptr) return true; + if (vtable == vtable_ptr()) return true; if (!hasChildren()) return false; } return is_subclass(get(instance_ptr)); @@ -411,20 +469,20 @@ namespace DFHack bool is_direct_instance(virtual_ptr instance_ptr) const { if (!instance_ptr) return false; - return vtable_ptr ? (vtable_ptr == get_vtable(instance_ptr)) - : (this == get(instance_ptr)); + auto vp = vtable_ptr(); + return vp ? (vp == get_vtable(instance_ptr)) : (this == get(instance_ptr)); } template static P get_vmethod_ptr(P selector); public: - bool can_instantiate() { return can_allocate(); } - virtual_ptr instantiate() { return can_instantiate() ? (virtual_ptr)do_allocate() : NULL; } + bool can_instantiate() const { return can_allocate(); } + virtual_ptr instantiate() const { return can_instantiate() ? (virtual_ptr)do_allocate() : NULL; } static virtual_ptr clone(virtual_ptr obj); public: // Strictly for use in virtual class constructors - void adjust_vtable(virtual_ptr obj, virtual_identity *main); + void adjust_vtable(virtual_ptr obj, const virtual_identity *main) const; }; template @@ -494,52 +552,78 @@ namespace df using DFHack::DfLinkedList; using DFHack::DfOtherVectors; + /* + * + * Allocator functions are used to allocate, deallocate, and copy-assign objects + * + * When out is non-null, the object pointed to by in is copy-assigned over the object + * pointed to by out, if possible. When assignment is possible, out is returned. + * When assignment is not possible, nothing is done and nullptr is returned. + * The type must be copy-assignable for this to work; move-assignment is not + * supported. Callers can determine if the assignment succeeded by checking for the + * return value matching out (or simply being not null). + * + * When only in is non-null, the object pointed to by in is destroyed and deallocated, + * and in is returned. Note that the return value points to deallocated memory + * and should not be dereferenced. + * + * When both out and in are null, a new object is constructed and returned. + * + * Calling an allocator function with out non-null and in null is undefined behavior. + * + */ + + using df_pool_id_t = size_t; + template concept pooled_object = requires () { { T::pool_id } -> std::convertible_to; }; + template concept copy_assignable = std::assignable_from && std::assignable_from; template - void* allocator_try_assign(void *out, const void *in) { - if constexpr (copy_assignable) { - *(T*)out = *(const T*)in; - return out; - } - else { - // assignment is not possible; do nothing - return nullptr; - } - } + void *allocator_fn(void *out, const void *in) { + constexpr df_pool_id_t invalid_pool_id = static_cast(-1); + // unerase type + T* _out = out ? reinterpret_cast(out) : nullptr; + const T* _in = in ? reinterpret_cast(in) : nullptr; + if (_out) + { + if constexpr (copy_assignable) + { + *_out = *_in; + return out; + } + else + { + return nullptr; + } + } + else if (_in) + { + if constexpr (pooled_object) + { + if (_in->pool_id != invalid_pool_id) + { + throw std::runtime_error("Pool-allocated type cannot be deallocated with allocator_fn"); + } + } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" - template - void *allocator_fn(void *out, const void *in) { - if (out) { return allocator_try_assign(out, in); } - else if (in) { delete (T*)in; return (T*)in; } - else return new T(); - } + delete _in; #pragma GCC diagnostic pop - - template - void *allocator_nodel_fn(void *out, const void *in) { - if (out) { *(T*)out = *(const T*)in; return out; } - else if (in) { return NULL; } - else return new T(); - } - - template - void *allocator_noassign_fn(void *out, const void *in) { - if (out) { return NULL; } - else if (in) { delete (T*)in; return (T*)in; } - else return new T(); + return const_cast(in); + } + else + return new T(); } template struct identity_traits {}; template - requires requires () { { &T::_identity } -> std::convertible_to; } + requires requires () { { &T::_identity } -> std::convertible_to; } struct identity_traits { static const bool is_primitive = false; - static compound_identity *get() { return &T::_identity; } + static const compound_identity *get() { return &T::_identity; } }; template @@ -564,6 +648,10 @@ namespace df enum_field &operator=(EnumType ev) { value = IntType(ev); return *this; } + explicit operator IntType () const { return IntType(value); } + template + explicit operator T () const { return static_cast(IntType(value)); } + }; template @@ -881,7 +969,7 @@ namespace DFHack { inline void flagarray_to_string(std::vector *pvec, const BitArray &val) { typedef df::enum_traits traits; int size = traits::last_item_value-traits::first_item_value+1; - flagarrayToString(pvec, val.bits, val.size, + flagarrayToString(pvec, val.bits(), val.size(), (int)traits::first_item_value, size, traits::key_table); } @@ -914,7 +1002,7 @@ namespace DFHack { #define ENUM_KEY_STR(enum,val) (DFHack::enum_item_key(val)) #define ENUM_FIRST_ITEM(enum) (df::enum_traits::first_item) #define ENUM_LAST_ITEM(enum) (df::enum_traits::last_item) - +#define ENUM_AS_STR(val) (DFHack::enum_item_key(val)) #define ENUM_NEXT_ITEM(enum,val) \ (DFHack::next_enum_item(val)) #define FOR_ENUM_ITEMS(enum,iter) \ @@ -942,3 +1030,25 @@ namespace std { } }; } + +template <> +struct fmt::formatter : fmt::formatter +{ + template + auto format(const df::coord& c, FormatContext& ctx) const + { + return fmt::formatter::format( + fmt::format("({}, {}, {})", c.x, c.y, c.z), ctx); + } +}; + +template <> +struct fmt::formatter : fmt::formatter +{ + template + auto format(const df::coord2d& c, FormatContext& ctx) const + { + return fmt::formatter::format( + fmt::format("({}, {})", c.x, c.y), ctx); + } +}; diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index 0b297a68e4..ab9bb78a26 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -24,10 +24,6 @@ distribution. #pragma once -#include -#include -#include -#include #include #include "ColorText.h" diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index e9fb5a9aa3..cf486e6188 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -26,14 +26,13 @@ distribution. #include #include -#include #include -#include #include +#include #include +#include #include #include -#include #include #include "DataDefs.h" @@ -524,7 +523,7 @@ namespace df protected: virtual int item_count(void *ptr, CountMode cnt) const { - return cnt == COUNT_LEN ? ((container*)ptr)->size * 8 : -1; + return cnt == COUNT_LEN ? ((container*)ptr)->size() * 8 : -1; } virtual bool get_item(void *ptr, int idx) const { return ((container*)ptr)->is_set(idx); @@ -618,8 +617,10 @@ namespace df INTEGER_IDENTITY_TRAITS(unsigned long); INTEGER_IDENTITY_TRAITS(long long); INTEGER_IDENTITY_TRAITS(unsigned long long); + INTEGER_IDENTITY_TRAITS(wchar_t); FLOAT_IDENTITY_TRAITS(float); FLOAT_IDENTITY_TRAITS(double); + OPAQUE_IDENTITY_TRAITS(wchar_t*); OPAQUE_IDENTITY_TRAITS(std::condition_variable); OPAQUE_IDENTITY_TRAITS(std::fstream); OPAQUE_IDENTITY_TRAITS(std::mutex); @@ -638,7 +639,7 @@ namespace df static opaque_identity *get() { using type = std::shared_ptr; static std::string name = std::string("shared_ptr<") + typeid(T).name() + ">"; - static opaque_identity identity(sizeof(type), allocator_noassign_fn, name); + static opaque_identity identity(sizeof(type), allocator_fn, name); return &identity; } }; @@ -712,6 +713,11 @@ namespace df static const container_identity *get(); }; + template struct identity_traits> + { + static const container_identity* get(); + }; + template struct identity_traits > { static const container_identity *get(); }; @@ -750,6 +756,11 @@ namespace df static const container_identity *get(); }; + template struct identity_traits > + { + static const container_identity* get(); + }; + template<> struct identity_traits > { static const bit_array_identity identity; static const bit_container_identity *get() { return &identity; } @@ -791,6 +802,13 @@ namespace df return &identity; } + template + inline const container_identity* identity_traits>::get() + { + static const buffer_container_identity identity(sz, identity_traits::get()); + return &identity; + } + template inline const container_identity *identity_traits >::get() { using container = std::vector; @@ -833,6 +851,14 @@ namespace df return &identity; } + template + inline const container_identity* identity_traits >::get() + { + using container = std::unordered_set; + static const ro_stl_container_identity identity("unordered_set", identity_traits::get()); + return &identity; + } + template inline const container_identity *identity_traits>::get() { using container = std::map; diff --git a/library/include/Debug.h b/library/include/Debug.h index 48e661acc4..632d4910b6 100644 --- a/library/include/Debug.h +++ b/library/include/Debug.h @@ -183,7 +183,7 @@ class DFHACK_EXPORT DebugCategory final { }; /*! - * Fetch a steam object proxy object for output. It also adds standard + * Fetch a stream object proxy object for output. It also adds standard * message components like time and plugin and category names to the line. * * User must make sure that the line is terminated with a line end. @@ -194,6 +194,13 @@ class DFHACK_EXPORT DebugCategory final { */ ostream_proxy_prefix getStream(const level msgLevel) const { + // if the core instance is unavailable, use stderr as a fallback + if (Core::noInstance()) + { + static color_ostream_wrapper fallback{std::cerr}; + return {*this,fallback,msgLevel}; + } + return {*this,Core::getInstance().getConsole(),msgLevel}; } /*! diff --git a/library/include/Error.h b/library/include/Error.h index a4624d5f72..550e1b0198 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -25,7 +25,6 @@ distribution. #pragma once #include -#include #include #include "Export.h" diff --git a/library/include/Format.h b/library/include/Format.h new file mode 100644 index 0000000000..52d4e0e3c6 --- /dev/null +++ b/library/include/Format.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef USE_FMTLIB + +#include +#include +#include + +#else + +#include + +namespace fmt = std; + +#endif diff --git a/library/include/Hooks.h b/library/include/Hooks.h index 6945de2ea6..5f49d8daba 100644 --- a/library/include/Hooks.h +++ b/library/include/Hooks.h @@ -26,6 +26,7 @@ distribution. union SDL_Event; +DFhackCExport void dfhooks_preinit(std::filesystem::path dllpath); DFhackCExport void dfhooks_init(); DFhackCExport void dfhooks_shutdown(); DFhackCExport void dfhooks_update(); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index db40e76810..f95095d55b 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -24,22 +24,22 @@ distribution. #pragma once +#include #include -#include -#include -#include #include +#include +#include +#include #include #include #include -#include +#include #include "Core.h" #include "ColorText.h" #include "DataDefs.h" #include "df/interface_key.h" -#include "df/interfacest.h" #include #include @@ -140,14 +140,14 @@ namespace DFHack::Lua { * Return behavior is of SafeCall below. */ DFHACK_EXPORT bool AssignDFObject(color_ostream &out, lua_State *state, - type_identity *type, void *target, int val_index, + const type_identity *type, void *target, int val_index, bool exact_type = false, bool perr = true); /** * Assign the value at val_index to the target of given identity using df.assign(). * Otherwise throws an error. */ - DFHACK_EXPORT void CheckDFAssign(lua_State *state, type_identity *type, + DFHACK_EXPORT void CheckDFAssign(lua_State *state, const type_identity *type, void *target, int val_index, bool exact_type = false); template concept df_object = requires() @@ -288,7 +288,7 @@ namespace DFHack::Lua { * Uses RunCoreQueryLoop internally. */ DFHACK_EXPORT bool InterpreterLoop(color_ostream &out, lua_State *state, - const char *prompt = NULL, const char *hfile = NULL); + std::string_view prompt = {}, std::filesystem::path hfile = {}); /** * Run an interactive prompt loop. All access to the lua state diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 67301b52cc..70f36d3f95 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -24,13 +24,9 @@ distribution. #pragma once -#include -#include -#include -#include - #include -#include + +#include "DataDefs.h" /** * Internal header file of the lua wrapper. @@ -167,7 +163,7 @@ namespace LuaWrapper { const char *ctx, bool allow_type = false, bool keep_metatable = false); - void LookupInTable(lua_State *state, void *id, LuaToken *tname); + void LookupInTable(lua_State *state, const void *id, LuaToken *tname); void SaveInTable(lua_State *state, void *node, LuaToken *tname); void SaveTypeInfo(lua_State *state, void *node); diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 95f7e256d1..5115348ba2 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -29,7 +29,6 @@ distribution. #define PROCESS_H_INCLUDED #include "Export.h" -#include #include #include #include @@ -51,9 +50,9 @@ namespace DFHack */ struct DFHACK_EXPORT t_memrange { - void * base; - void * start; - void * end; + void* base; + void* start; + void* end; // memory range name (if any) char name[1024]; // permission to read @@ -64,7 +63,7 @@ namespace DFHack bool execute : 1; // is a shared region bool shared : 1; - inline bool isInRange( void * address) + inline bool isInRange(void* address) { if (address >= start && address < end) return true; return false; @@ -78,237 +77,242 @@ namespace DFHack */ class DFHACK_EXPORT Process { - public: - /// this is the single most important destructor ever. ~px - Process(const VersionInfoFactory& known_versions); - ~Process(); - /// read a 8-byte integer - uint64_t readQuad(const void * address) - { - return *(uint64_t *)address; - } - /// read a 8-byte integer - void readQuad(const void * address, uint64_t & value) - { - value = *(uint64_t *)address; - }; - /// write a 8-byte integer - void writeQuad(const void * address, const uint64_t value) - { - (*(uint64_t *)address) = value; - }; - - /// read a 4-byte integer - uint32_t readDWord(const void * address) - { - return *(uint32_t *)address; - } - /// read a 4-byte integer - void readDWord(const void * address, uint32_t & value) - { - value = *(uint32_t *)address; - }; - /// write a 4-byte integer - void writeDWord(const void * address, const uint32_t value) - { - (*(uint32_t *)address) = value; - }; - - /// read a pointer - char * readPtr(const void * address) - { - return *(char **)address; - } - /// read a pointer - void readPtr(const void * address, char * & value) - { - value = *(char **)address; - }; - - /// read a float - float readFloat(const void * address) - { - return *(float*)address; - } - /// write a float - void readFloat(const void * address, float & value) - { - value = *(float*)address; - }; - - /// read a 2-byte integer - uint16_t readWord(const void * address) - { - return *(uint16_t *)address; - } - /// read a 2-byte integer - void readWord(const void * address, uint16_t & value) - { - value = *(uint16_t *)address; - }; - /// write a 2-byte integer - void writeWord(const void * address, const uint16_t value) - { - (*(uint16_t *)address) = value; - }; - - /// read a byte - uint8_t readByte(const void * address) - { - return *(uint8_t *)address; - } - /// read a byte - void readByte(const void * address, uint8_t & value) - { - value = *(uint8_t *)address; - }; - /// write a byte - void writeByte(const void * address, const uint8_t value) - { - (*(uint8_t *)address) = value; - }; - - /// read an arbitrary amount of bytes - void read(void * address, uint32_t length, uint8_t* buffer) - { - memcpy(buffer, (void *) address, length); - }; - /// write an arbitrary amount of bytes - void write(void * address, uint32_t length, uint8_t* buffer) - { - memcpy((void *) address, buffer, length); - }; - - /// read an STL string - const std::string readSTLString (void * offset) - { - std::string * str = (std::string *) offset; - return *str; - }; - /// read an STL string - size_t readSTLString (void * offset, char * buffer, size_t bufcapacity) - { - if(!bufcapacity || bufcapacity == 1) - return 0; - std::string * str = (std::string *) offset; - size_t copied = str->copy(buffer,bufcapacity-1); - buffer[copied] = 0; - return copied; - }; - /** - * write an STL string - * @return length written - */ - size_t writeSTLString(const void * address, const std::string writeString) - { - std::string * str = (std::string *) address; - str->assign(writeString); - return writeString.size(); - }; - /** - * attempt to copy a string from source address to target address. may truncate or leak, depending on platform - * @return length copied - */ - size_t copySTLString(const void * address, const uintptr_t target) - { - std::string * strsrc = (std::string *) address; - std::string * str = (std::string *) target; - str->assign(*strsrc); - return str->size(); - } - - /// get class name of an object with rtti/type info - std::string doReadClassName(void * vptr); - - std::string readClassName(void * vptr) - { - std::map::iterator it = classNameCache.find(vptr); - if (it != classNameCache.end()) - return it->second; - return classNameCache[vptr] = doReadClassName(vptr); - } - - /// read a null-terminated C string - const std::string readCString (void * offset) - { - return std::string((char *) offset); - }; - - /// @return true if the process is suspended - bool isSuspended() - { - return true; - }; - /// @return true if the process is identified -- has a symbol table extension - bool isIdentified() - { - return identified; - }; - - /// get virtual memory ranges of the process (what is mapped where) - static void getMemRanges(std::vector & ranges); - - /// get the symbol table extension of this process - std::shared_ptr getDescriptor() - { - return my_descriptor; - }; - - void ValidateDescriptionOS() { - if (my_descriptor) - my_descriptor->ValidateOS(); - }; - - uintptr_t getBase(); - /// get the DF Process ID - int getPID(); - /// get the DF Process FilePath - std::filesystem::path getPath(); - /// Adjust between in-memory and in-file image offset - int adjustOffset(int offset, bool to_file = false); - - /// millisecond tick count, exactly as DF uses - uint32_t getTickCount(); - - /// modify permisions of memory range - bool setPermissions(const t_memrange & range,const t_memrange &trgrange); - - /// write a possibly read-only memory area - bool patchMemory(void *target, const void* src, size_t count); - - /// flush cache - bool flushCache(const void* target, size_t count); - - /// allocate new memory pages for code or stuff - /// returns -1 on error (0 is a valid address) - void* memAlloc(const int length); - - /// free memory pages from memAlloc - /// should have length = alloced length for portability - /// returns 0 on success - int memDealloc(void *ptr, const int length); - - /// change memory page permissions - /// prot is a bitwise OR of the MemProt enum - /// returns 0 on success - int memProtect(void *ptr, const int length, const int prot); - - enum MemProt { - READ = 1, - WRITE = 2, - EXEC = 4 - }; - - uint32_t getPE() { return my_pe; } - std::string getMD5() { return my_md5; } + public: + /// this is the single most important destructor ever. ~px + Process(const VersionInfoFactory& known_versions); + ~Process(); + /// read a 8-byte integer + uint64_t readQuad(const void* address) + { + return *(uint64_t*)address; + } + /// read a 8-byte integer + void readQuad(const void* address, uint64_t& value) + { + value = *(uint64_t*)address; + }; + /// write a 8-byte integer + void writeQuad(const void* address, const uint64_t value) + { + (*(uint64_t*)address) = value; + }; + + /// read a 4-byte integer + uint32_t readDWord(const void* address) + { + return *(uint32_t*)address; + } + /// read a 4-byte integer + void readDWord(const void* address, uint32_t& value) + { + value = *(uint32_t*)address; + }; + /// write a 4-byte integer + void writeDWord(const void* address, const uint32_t value) + { + (*(uint32_t*)address) = value; + }; + + /// read a pointer + char* readPtr(const void* address) + { + return *(char**)address; + } + /// read a pointer + void readPtr(const void* address, char*& value) + { + value = *(char**)address; + }; + + /// read a float + float readFloat(const void* address) + { + return *(float*)address; + } + /// write a float + void readFloat(const void* address, float& value) + { + value = *(float*)address; + }; + + /// read a 2-byte integer + uint16_t readWord(const void* address) + { + return *(uint16_t*)address; + } + /// read a 2-byte integer + void readWord(const void* address, uint16_t& value) + { + value = *(uint16_t*)address; + }; + /// write a 2-byte integer + void writeWord(const void* address, const uint16_t value) + { + (*(uint16_t*)address) = value; + }; + + /// read a byte + uint8_t readByte(const void* address) + { + return *(uint8_t*)address; + } + /// read a byte + void readByte(const void* address, uint8_t& value) + { + value = *(uint8_t*)address; + }; + /// write a byte + void writeByte(const void* address, const uint8_t value) + { + (*(uint8_t*)address) = value; + }; + + /// read an arbitrary amount of bytes + void read(void* address, uint32_t length, uint8_t* buffer) + { + memcpy(buffer, (void*)address, length); + }; + /// write an arbitrary amount of bytes + void write(void* address, uint32_t length, uint8_t* buffer) + { + memcpy((void*)address, buffer, length); + }; + + /// read an STL string + const std::string readSTLString(void* offset) + { + std::string* str = (std::string*)offset; + return *str; + }; + /// read an STL string + size_t readSTLString(void* offset, char* buffer, size_t bufcapacity) + { + if (!bufcapacity || bufcapacity == 1) + return 0; + std::string* str = (std::string*)offset; + size_t copied = str->copy(buffer, bufcapacity - 1); + buffer[copied] = 0; + return copied; + }; + /** + * write an STL string + * @return length written + */ + size_t writeSTLString(const void* address, const std::string writeString) + { + std::string* str = (std::string*)address; + str->assign(writeString); + return writeString.size(); + }; + /** + * attempt to copy a string from source address to target address. may truncate or leak, depending on platform + * @return length copied + */ + size_t copySTLString(const void* address, const uintptr_t target) + { + std::string* strsrc = (std::string*)address; + std::string* str = (std::string*)target; + str->assign(*strsrc); + return str->size(); + } + + /// get class name of an object with rtti/type info + std::string doReadClassName(void* vptr); + + std::string readClassName(void* vptr) + { + auto it = classNameCache.find(vptr); + if (it != classNameCache.end()) + return it->second; + return classNameCache[vptr] = doReadClassName(vptr); + } + + /// read a null-terminated C string + const std::string readCString(void* offset) + { + return std::string((char*)offset); + }; + + /// @return true if the process is suspended + bool isSuspended() + { + return true; + }; + /// @return true if the process is identified -- has a symbol table extension + bool isIdentified() + { + return identified; + }; + + /// get virtual memory ranges of the process (what is mapped where) + static void getMemRanges(std::vector& ranges); + + /// check if an address has a mapping + bool checkValidAddress(void* ptr); + + /// get the symbol table extension of this process + std::shared_ptr getDescriptor() + { + return my_descriptor; + }; + + void ValidateDescriptionOS() + { + if (my_descriptor) + my_descriptor->ValidateOS(); + }; + + uintptr_t getBase(); + /// get the DF Process ID + int getPID(); + /// get the DF Process FilePath + std::filesystem::path getPath(); + /// Adjust between in-memory and in-file image offset + int adjustOffset(int offset, bool to_file = false); + + /// millisecond tick count, exactly as DF uses + uint32_t getTickCount(); + + /// modify permisions of memory range + bool setPermissions(const t_memrange& range, const t_memrange& trgrange); + + /// write a possibly read-only memory area + bool patchMemory(void* target, const void* src, size_t count); + + /// flush cache + bool flushCache(const void* target, size_t count); + + /// allocate new memory pages for code or stuff + /// returns -1 on error (0 is a valid address) + void* memAlloc(const int length); + + /// free memory pages from memAlloc + /// should have length = alloced length for portability + /// returns 0 on success + int memDealloc(void* ptr, const int length); + + /// change memory page permissions + /// prot is a bitwise OR of the MemProt enum + /// returns 0 on success + int memProtect(void* ptr, const int length, const int prot); + + enum MemProt + { + READ = 1, + WRITE = 2, + EXEC = 4 + }; + + uint32_t getPE() { return my_pe; } + std::string getMD5() { return my_md5; } private: std::shared_ptr my_descriptor; - PlatformSpecific *d; + PlatformSpecific* d; bool identified; uint32_t my_pid; uint32_t base; - std::map classNameCache; + std::map classNameCache; uint32_t my_pe; std::string my_md5; }; @@ -316,36 +320,21 @@ namespace DFHack class DFHACK_EXPORT ClassNameCheck { std::string name; - mutable void * vptr; + mutable void* vptr; public: ClassNameCheck() : vptr(0) {} ClassNameCheck(std::string _name); - ClassNameCheck &operator= (const ClassNameCheck &b); + ClassNameCheck& operator= (const ClassNameCheck& b); // Is the class name of the given virtual table pointer the same as the // name for thei ClassNameCheck object? - bool operator() (Process *p, void * ptr) const; + bool operator() (Process* p, void* ptr) const; // Get list of names given to ClassNameCheck constructors. - static void getKnownClassNames(std::vector &names); + static void getKnownClassNames(std::vector& names); }; - class DFHACK_EXPORT MemoryPatcher - { - Process *p; - std::vector ranges, save; - public: - MemoryPatcher(Process *p = NULL); - ~MemoryPatcher(); - - bool verifyAccess(void *target, size_t size, bool write = false); - bool makeWritable(void *target, size_t size) { - return verifyAccess(target, size, true); - } - bool write(void *target, const void *src, size_t size); - - void close(); - }; } + #endif diff --git a/library/include/MemoryPatcher.h b/library/include/MemoryPatcher.h new file mode 100644 index 0000000000..8577a762ef --- /dev/null +++ b/library/include/MemoryPatcher.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include "MemAccess.h" + +namespace DFHack { + + class Process; + + class MemoryPatcher { + public: + explicit MemoryPatcher(Process *p_ = nullptr); + ~MemoryPatcher(); + + // Ensure the target memory region is accessible and optionally writable. + // If `write` is true this will attempt to make the pages writable. + bool verifyAccess(void *target, size_t count, bool write); + + // Write `size` bytes from `src` to `target`. Returns true on success. + bool write(void *target, const void *src, size_t size); + + // Restore any modified permissions and clear internal state. + void close(); + + bool makeWritable(void* target, size_t size) + { + return verifyAccess(target, size, true); + } + private: + Process* p; + std::vector ranges, save; + }; + +} diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index 2b9426202d..e8d904e81b 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -33,7 +33,6 @@ distribution. #include #include #include -#include #include #include #include @@ -400,7 +399,7 @@ typename T::mapped_type findPrefixInMap( #endif template -inline bool static_add_to_map(CT *pmap, typename CT::key_type key, typename CT::mapped_type value) { +inline bool static_add_to_map(CT *pmap, const typename CT::key_type key, const typename CT::mapped_type value) { (*pmap)[key] = value; return true; } @@ -475,6 +474,19 @@ static inline std::string &trim(std::string &s) { return ltrim(rtrim(s)); } +static inline bool has_backslashes(const std::string_view str) +{ + return (str.find('\\') != std::string::npos); +} + +static inline void replace_backslashes_with_forwardslashes(std::string& str) +{ + for (auto& c : str) { + if (c == '\\') + c = '/'; + } +} + enum struct NumberFormatType : int32_t { DEFAULT = 0, ENGLISH, diff --git a/library/include/Module.h b/library/include/Module.h deleted file mode 100644 index 7770e13970..0000000000 --- a/library/include/Module.h +++ /dev/null @@ -1,59 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#pragma once - -#ifndef MODULE_H_INCLUDED -#define MODULE_H_INCLUDED - -#include "Export.h" -namespace DFHack -{ - /** - * The parent class for all DFHack modules - * \ingroup grp_modules - */ - class DFHACK_EXPORT Module - { - public: - virtual ~Module(){}; - virtual bool Start(){return true;};// default start... - virtual bool Finish() = 0;// everything should have a Finish() - /* - // should Context call Finish when Resume is called? - virtual bool OnResume() - { - Finish(); - return true; - }; - // Finish when map change is detected? - // TODO: implement - virtual bool OnMapChange() - { - return false; - }; - */ - }; -} -#endif //MODULE_H_INCLUDED diff --git a/library/include/ModuleFactory.h b/library/include/ModuleFactory.h deleted file mode 100644 index c99e7b3289..0000000000 --- a/library/include/ModuleFactory.h +++ /dev/null @@ -1,38 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#pragma once - -#ifndef MODULE_FACTORY_H_INCLUDED -#define MODULE_FACTORY_H_INCLUDED - -#include - -namespace DFHack -{ - class Module; - std::unique_ptr createMaterials(); - std::unique_ptr createGraphic(); -} -#endif diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 50b9b0447d..1eccc7a79a 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -25,7 +25,6 @@ distribution. #pragma once #include "Export.h" -#include "Hooks.h" #include "ColorText.h" #include "MiscUtils.h" #include diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index 8a5bee86f7..bade25b521 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -211,6 +211,7 @@ namespace DFHack functions.push_back(new VoidServerMethod(this, name, flags, fptr)); } + public: void dumpMethods(std::ostream & out) const; }; diff --git a/library/include/RemoteTools.h b/library/include/RemoteTools.h index 38c234c5fb..8731ad3474 100644 --- a/library/include/RemoteTools.h +++ b/library/include/RemoteTools.h @@ -74,7 +74,7 @@ namespace DFHack */ template void flagarray_to_ints(RepeatedField *pf, const BitArray &val) { - for (size_t i = 0; i < val.size*8; i++) + for (size_t i = 0; i < size_t(val.size()*8); i++) if (val.is_set(T(i))) pf->Add(i); } diff --git a/library/include/Types.h b/library/include/Types.h index 84408ecf2a..f367946e56 100644 --- a/library/include/Types.h +++ b/library/include/Types.h @@ -30,12 +30,10 @@ distribution. #include #include "Export.h" - #include "DataDefs.h" #include "df/general_ref_type.h" #include "df/specific_ref_type.h" -#include "df/language_name_type.h" namespace df { struct building; diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h index 7575ee18c2..9dff74d7e5 100644 --- a/library/include/VTableInterpose.h +++ b/library/include/VTableInterpose.h @@ -25,7 +25,8 @@ distribution. #pragma once #include "DataDefs.h" -#include "DataIdentity.h" + +#include namespace DFHack { diff --git a/library/include/VersionInfoFactory.h b/library/include/VersionInfoFactory.h index 060d622ecd..92a5f94e10 100644 --- a/library/include/VersionInfoFactory.h +++ b/library/include/VersionInfoFactory.h @@ -26,6 +26,7 @@ distribution. #pragma once #include +#include #include "Export.h" @@ -38,7 +39,7 @@ namespace DFHack public: VersionInfoFactory(); ~VersionInfoFactory(); - bool loadFile( std::string path_to_xml); + bool loadFile( std::filesystem::path path_to_xml); bool isInErrorState() const {return error;}; std::shared_ptr getVersionInfoByMD5(std::string md5string) const; std::shared_ptr getVersionInfoByPETimestamp(uintptr_t timestamp) const; diff --git a/library/include/df/custom/.gitignore b/library/include/df/custom/.gitignore new file mode 100644 index 0000000000..96e79c167d --- /dev/null +++ b/library/include/df/custom/.gitignore @@ -0,0 +1 @@ +!*.h diff --git a/library/include/df/custom/hash/labor_kitchen_interface_food_key.h b/library/include/df/custom/hash/labor_kitchen_interface_food_key.h new file mode 100644 index 0000000000..5f2d0d2b33 --- /dev/null +++ b/library/include/df/custom/hash/labor_kitchen_interface_food_key.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include "df/labor_kitchen_interface_food_key.h" + +namespace std +{ + template<> + struct hash + { + auto operator()(const df::labor_kitchen_interface_food_key& a) const -> size_t + { + struct thing + { + int16_t t; + int16_t st; + int32_t x; + } thing{ + .t = a.type, + .st = a.subtype, + .x = static_cast(a.mat) ^ (static_cast(a.matg)) + }; + + return hash()(std::bit_cast(thing)); + } + }; +} diff --git a/library/include/df/custom/hash/texture_fullid.h b/library/include/df/custom/hash/texture_fullid.h new file mode 100644 index 0000000000..a1e4606e33 --- /dev/null +++ b/library/include/df/custom/hash/texture_fullid.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include "df/texture_fullid.h" + +template<> +struct std::hash +{ + size_t operator()(df::texture_fullid const &t) const noexcept + { +// for some reason, bay12 used different hash methods on windows vs linux +#ifdef WIN32 + size_t h=std::hash{}(t.texpos); + auto u_hash=std::hash{}; + h^=u_hash(std::bit_cast(std::make_pair(t.r, t.g))); + h^=u_hash(std::bit_cast(std::make_pair(t.b, t.br)))<<1; + h^=u_hash(std::bit_cast(std::make_pair(t.bg, t.bb)))<<2; + h^=std::hash{}(t.flag.whole); + return h; +#else + size_t h=std::hash{}(t.texpos); + auto u_hash=std::hash{}; + h^=u_hash(t.r); + h^=u_hash(t.g)<<1; + h^=u_hash(t.b)<<2; + h^=u_hash(t.br)<<3; + h^=u_hash(t.bg)<<4; + h^=u_hash(t.bb)<<5; + h^=std::hash{}(t.flag.whole)<<6; + return h; +#endif + } +}; diff --git a/library/include/df/custom/labor_kitchen_interface_food_key.methods.inc b/library/include/df/custom/labor_kitchen_interface_food_key.methods.inc new file mode 100644 index 0000000000..a1de2fec13 --- /dev/null +++ b/library/include/df/custom/labor_kitchen_interface_food_key.methods.inc @@ -0,0 +1,11 @@ + bool operator<(const df::labor_kitchen_interface_food_key &b) const { + if (typetexpos == other.texpos && + this->r == other.r && + this->g == other.g && + this->b == other.b && + this->br == other.br && + this->bg == other.bg && + this->bb == other.bb && + this->flag.whole == other.flag.whole + ); +} + +auto operator< (const texture_fullid &other) const { + if (this->texpos < other.texpos) return true; + if (this->r < other.r) return true; + if (this->g < other.g) return true; + if (this->b < other.b) return true; + if (this->br < other.br) return true; + if (this->bg < other.bg) return true; + if (this->bb < other.bb) return true; + if (this->flag.whole < other.flag.whole) return true; + return false; +} diff --git a/library/include/df/custom/tile_bitmask.methods.inc b/library/include/df/custom/tile_bitmask.methods.inc index b991819b94..ade00e7218 100644 --- a/library/include/df/custom/tile_bitmask.methods.inc +++ b/library/include/df/custom/tile_bitmask.methods.inc @@ -6,11 +6,11 @@ inline uint16_t &operator[] (int y) } void clear() { - memset(bits,0,sizeof(bits)); + bits.fill(0); } void set_all() { - memset(bits,0xFF,sizeof(bits)); + bits.fill(-1); } inline bool getassignment( const df::coord2d &xy ) { diff --git a/library/include/modules/Burrows.h b/library/include/modules/Burrows.h index b2e3e56b4e..dbd2e51dd6 100644 --- a/library/include/modules/Burrows.h +++ b/library/include/modules/Burrows.h @@ -45,6 +45,8 @@ namespace DFHack { namespace Burrows { + DFHACK_EXPORT std::string getName(df::burrow* burrow); + DFHACK_EXPORT df::burrow *findByName(std::string name, bool ignore_final_plus = false); // Units diff --git a/library/include/modules/DFSDL.h b/library/include/modules/DFSDL.h index 8d12de1918..7d53242ad0 100644 --- a/library/include/modules/DFSDL.h +++ b/library/include/modules/DFSDL.h @@ -1,27 +1,20 @@ #pragma once -#include "Error.h" -#include "Export.h" #include "ColorText.h" +#include "Export.h" +#include +#include +#include #include struct SDL_Surface; struct SDL_Rect; +struct SDL_Renderer; struct SDL_PixelFormat; struct SDL_Window; union SDL_Event; - -namespace DFHack -{ - struct DFTileSurface - { - bool paintOver; // draw over original tile? - SDL_Surface* surface; // from where it should be drawn - SDL_Rect* rect; // from which coords (NULL to draw whole surface) - SDL_Rect* dstResize; // if not NULL dst rect will be resized (x/y/w/h will be added to original dst) - }; -} +using SDL_Keycode = int32_t; /** * The DFSDL module - provides access to SDL functions without actually @@ -56,6 +49,10 @@ namespace DFHack::DFSDL DFHACK_EXPORT SDL_Surface* DFSDL_CreateRGBSurfaceWithFormat(uint32_t flags, int width, int height, int depth, uint32_t format); DFHACK_EXPORT int DFSDL_ShowSimpleMessageBox(uint32_t flags, const char* title, const char* message, SDL_Window* window); + DFHACK_EXPORT uint32_t DFSDL_GetMouseState(int* x, int* y); + DFHACK_EXPORT void DFSDL_RenderWindowToLogical(SDL_Renderer* renderer, int windowX, int windowY, float* logicalX, float* logicalY); + DFHACK_EXPORT void DFSDL_RenderLogicalToWindow(SDL_Renderer* renderer, float logicalX, float logicalY, int* windowX, int* windowY); + // submitted and returned text is UTF-8 // see wrapper functions below for cp-437 variants DFHACK_EXPORT char* DFSDL_GetClipboardText(); @@ -64,6 +61,8 @@ namespace DFHack::DFSDL DFHACK_EXPORT char* DFSDL_GetPrefPath(const char* org, const char* app); DFHACK_EXPORT char* DFSDL_GetBasePath(); + DFHACK_EXPORT SDL_Keycode DFSDL_GetKeyFromName(const char* name); + DFHACK_EXPORT const char* DFSDL_GetKeyName(SDL_Keycode key); } namespace DFHack @@ -77,4 +76,7 @@ namespace DFHack DFHACK_EXPORT bool getClipboardTextCp437Multiline(std::vector * lines); DFHACK_EXPORT bool setClipboardTextCp437Multiline(std::string text); + // Queue a cb to be run on the render thread + DFHACK_EXPORT void runOnRenderThread(std::function cb); + DFHACK_EXPORT void runRenderThreadCallbacks(); } diff --git a/library/include/modules/DFSteam.h b/library/include/modules/DFSteam.h index e604294f8a..7080a94e2e 100644 --- a/library/include/modules/DFSteam.h +++ b/library/include/modules/DFSteam.h @@ -24,7 +24,7 @@ bool init(DFHack::color_ostream& out); /** * Call this when DFHack is being unloaded. */ -void cleanup(DFHack::color_ostream& out); +void cleanup(); DFHACK_EXPORT void launchSteamDFHackIfNecessary(DFHack::color_ostream& out); diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index e0a74bed92..a2c546a755 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -1,10 +1,8 @@ #pragma once -#include "Core.h" #include "Export.h" #include "ColorText.h" #include "PluginManager.h" -#include "Console.h" #include "DataDefs.h" #include "df/unit_inventory_item.h" diff --git a/library/include/modules/Filesystem.h b/library/include/modules/Filesystem.h index 407579926f..68da9ec7f6 100644 --- a/library/include/modules/Filesystem.h +++ b/library/include/modules/Filesystem.h @@ -77,12 +77,13 @@ namespace DFHack { DFHACK_EXPORT std::filesystem::path canonicalize(std::filesystem::path p) noexcept; inline std::string as_string(const std::filesystem::path path) noexcept { - auto pStr = path.string(); - if constexpr (std::filesystem::path::preferred_separator != '/') - { - std::ranges::replace(pStr, std::filesystem::path::preferred_separator, '/'); - } - return pStr; + // this just mashes the utf-8 into a std::string without any conversion + // this is largely because we use utf-8 everywhere internally as much as we can + // but we should ultimately convert to using u8strings for strings that are utf-8 + // and use a different string type for strings encoded in cp437 or in the locale codepage + std::u8string pstr = path.generic_u8string(); + return std::string((char*)pstr.c_str()); + } DFHACK_EXPORT std::filesystem::path getInstallDir() noexcept; DFHACK_EXPORT std::filesystem::path getBaseDir() noexcept; diff --git a/library/include/modules/Graphic.h b/library/include/modules/Graphic.h deleted file mode 100644 index 9fa498a7cf..0000000000 --- a/library/include/modules/Graphic.h +++ /dev/null @@ -1,59 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mr�zek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -/******************************************************************************* - GRAPHIC - Changing tile cache -*******************************************************************************/ -#pragma once -#ifndef CL_MOD_GRAPHIC -#define CL_MOD_GRAPHIC - -#include -#include "Export.h" -#include "Module.h" -#include - -namespace DFHack -{ - // forward declaration used here instead of including DFSDL.h to reduce inclusion loading - struct DFTileSurface; - - class DFHACK_EXPORT Graphic : public Module - { - public: - bool Finish() - { - return true; - } - bool Register(DFTileSurface* (*func)(int,int)); - bool Unregister(DFTileSurface* (*func)(int,int)); - DFTileSurface* Call(int x, int y); - - private: - std::vector funcs; - }; -} - -#endif diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 222779a8ee..761e948bb0 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -25,8 +25,6 @@ distribution. #pragma once #include "Export.h" -#include "Module.h" -#include "BitArray.h" #include "ColorText.h" #include "Types.h" #include "DataDefs.h" @@ -34,7 +32,6 @@ distribution. #include "modules/GuiHooks.h" #include "df/announcement_type.h" -#include "df/report_zoom_type.h" #include "df/unit_report_type.h" namespace df { @@ -211,7 +208,7 @@ namespace DFHack /// Get the current top-level view-screen DFHACK_EXPORT df::viewscreen *getCurViewscreen(bool skip_dismissed = false); - DFHACK_EXPORT df::viewscreen *getViewscreenByIdentity(virtual_identity &id, int n = 1); + DFHACK_EXPORT df::viewscreen *getViewscreenByIdentity(const virtual_identity &id, int n = 1); /// Get the top-most underlying DF viewscreen (not owned by DFHack) DFHACK_EXPORT df::viewscreen *getDFViewscreen(bool skip_dismissed = false, df::viewscreen *top = NULL); diff --git a/library/include/modules/Hotkey.h b/library/include/modules/Hotkey.h new file mode 100644 index 0000000000..25129b2ff5 --- /dev/null +++ b/library/include/modules/Hotkey.h @@ -0,0 +1,77 @@ +#pragma once + +#include "Export.h" +#include "ColorText.h" + +#include +#include +#include +#include +#include + +namespace DFHack { + namespace Hotkey { + class DFHACK_EXPORT KeySpec { + public: + int modifiers = 0; + // Negative numbers denote mouse buttons + int sym = 0; + std::vector focus; + + static std::optional parse(std::string spec, std::string* err = nullptr); + std::string toString(bool include_focus=true) const; + + // Determines if a keybind could be disruptive to normal gameplay, + // including typing and navigating the UI. + bool isDisruptive() const; + }; + + struct KeyBinding { + KeySpec spec; + std::string command; + std::string cmdline; + }; + } + class DFHACK_EXPORT HotkeyManager { + public: + HotkeyManager(); + ~HotkeyManager(); + + + bool addKeybind(std::string keyspec, std::string_view cmd); + bool addKeybind(Hotkey::KeySpec spec, std::string_view cmd); + // Clear a keybind with the given keyspec, optionally for any focus, or with a specific command + bool removeKeybind(std::string keyspec, bool match_focus=true, std::string_view cmdline=""); + bool removeKeybind(const Hotkey::KeySpec& spec, bool match_focus=true, std::string_view cmdline=""); + + std::vector listKeybinds(std::string keyspec); + std::vector listKeybinds(const Hotkey::KeySpec& spec); + + std::vector listActiveKeybinds(); + std::vector listAllKeybinds(); + + bool handleKeybind(int sym, int modifiers); + void setHotkeyCommand(std::string cmd); + + // Used to request the next hotkey-compatible input is saved. + // This is to allow for graphical keybinding menus. + void requestKeybindingInput(bool cancel=false); + // Returns the latest requested keybind input + std::string getKeybindingInput(); + + private: + std::thread hotkey_thread; + std::mutex lock; + std::condition_variable cond; + + bool keybind_save_requested = false; + std::string requested_keybind; + + uint8_t hotkey_sig = 0; + std::string queued_command; + + std::map> bindings; + + void hotkey_thread_fn(); + }; +} diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 1189fd8bc2..52af19e680 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -28,8 +28,6 @@ distribution. */ #include "DataDefs.h" #include "Export.h" -#include "MemAccess.h" -#include "Module.h" #include "Types.h" #include "modules/Materials.h" @@ -172,6 +170,9 @@ DFHACK_EXPORT int getItemBaseValue(int16_t item_type, int16_t item_subtype, int1 // Gets the value of a specific item, taking into account civ values and trade agreements if a caravan is given. DFHACK_EXPORT int getValue(df::item *item, df::caravan_state *caravan = NULL); +// Automatically choose a growth print variant for the specified plant growth subtype+material +DFHACK_EXPORT int32_t pickGrowthPrint(int16_t subtype, int16_t mat, int32_t matg); + DFHACK_EXPORT bool createItem(std::vector &out_items, df::unit *creator, df::item_type type, int16_t item_subtype, int16_t mat_type, int32_t mat_index, bool no_floor = false, int32_t count = 1); @@ -188,6 +189,9 @@ DFHACK_EXPORT bool markForTrade(df::item *item, df::building_tradedepotst *depot // Returns true if an active caravan will pay extra for the given item. DFHACK_EXPORT bool isRequestedTradeGood(df::item *item, df::caravan_state *caravan = NULL); +// DF standard_material_itemtype - returns true if item has material/matgloss, false if race+caste +DFHACK_EXPORT bool usesStandardMaterial(df::item_type item_type); + // Returns true if the item can currently be melted. If game_ui, then able to be marked is enough. DFHACK_EXPORT bool canMelt(df::item *item, bool game_ui = false); // Marks the item for melting. diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 5f7ddcb11a..acb37169c3 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -27,7 +27,6 @@ distribution. #define CL_MOD_JOB #include "Export.h" -#include "Module.h" #include "Types.h" #include "DataDefs.h" @@ -35,8 +34,6 @@ distribution. #include "df/item_type.h" #include "df/job_item_ref.h" -#include - namespace df { struct job; @@ -44,6 +41,7 @@ namespace df struct job_item_filter; struct building; struct unit; + struct manager_order; } namespace DFHack @@ -120,6 +118,7 @@ namespace DFHack int mat_index, df::item_type itype); DFHACK_EXPORT std::string getName(df::job *job); + DFHACK_EXPORT std::string getManagerOrderName(df::manager_order *order); struct JobDeleter { void operator()(df::job *ptr) const { diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index 0d63627389..31516dbb7f 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -68,8 +68,8 @@ struct BiomeInfo { int16_t layer_stone[MAX_LAYERS]; }; -typedef uint8_t t_veintype[16][16]; -typedef df::tiletype t_tilearr[16][16]; +using t_veintype = arr40d; +using t_tilearr = arr40d; class BlockInfo { diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 839989237e..aa8907ac7e 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -31,8 +31,6 @@ distribution. #define CL_MOD_MAPS #include "Export.h" -#include "Module.h" -#include "BitArray.h" #include "modules/Materials.h" @@ -40,6 +38,7 @@ distribution. #include "df/block_flags.h" #include "df/feature_type.h" #include "df/flow_type.h" +#include "df/matter_state.h" #include "df/tile_dig_designation.h" #include "df/tiletype.h" @@ -127,28 +126,32 @@ enum BiomeOffset { */ typedef df::block_flags t_blockflags; +template +using arr40d = std::array, 16>; + /** * 16x16 array of tile types * \ingroup grp_maps */ -typedef df::tiletype tiletypes40d [16][16]; +using tiletypes40d = arr40d; /** * 16x16 array used for squashed block materials * \ingroup grp_maps */ -typedef int16_t t_blockmaterials [16][16]; +using t_blockmaterials = arr40d; /** * 16x16 array of designation flags * \ingroup grp_maps */ typedef df::tile_designation t_designation; -typedef t_designation designations40d [16][16]; +using designations40d = arr40d; + /** * 16x16 array of occupancy flags * \ingroup grp_maps */ typedef df::tile_occupancy t_occupancy; -typedef t_occupancy occupancies40d [16][16]; +using occupancies40d = arr40d; /** * array of 16 biome indexes valid for the block * \ingroup grp_maps @@ -158,7 +161,7 @@ typedef uint8_t biome_indices40d [9]; * 16x16 array of temperatures * \ingroup grp_maps */ -typedef uint16_t t_temperatures [16][16]; +using t_temperatures = arr40d; /** * Index a tile array by a 2D coordinate, clipping it to mod 16. @@ -374,6 +377,10 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, std::vector *priorities = 0 ); +// Add spatters at the specified location, returning the amount that couldn't be placed (e.g. due to overflow) +extern DFHACK_EXPORT int32_t addMaterialSpatter (df::coord pos, int16_t mat, int32_t matg, df::matter_state state, int32_t amount); +extern DFHACK_EXPORT int32_t addItemSpatter (df::coord pos, df::item_type i_type, int16_t i_subtype, int16_t i_subcat1, int32_t i_subcat2, int32_t print_variant, int32_t amount); + // Remove a block event from the block by address. extern DFHACK_EXPORT bool RemoveBlockEvent(int32_t x, int32_t y, int32_t z, df::block_square_event *which ); extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event *which ); // TODO: deprecate me @@ -402,6 +409,8 @@ DFHACK_EXPORT bool removeTileAquifer(int32_t x, int32_t y, int32_t z); inline bool removeTileAquifer(df::coord pos) { return removeTileAquifer(pos.x, pos.y, pos.z); } DFHACK_EXPORT int removeAreaAquifer(df::coord pos1, df::coord pos2, std::function filter = [](df::coord pos, df::map_block *block) { return true; }); + +DFHACK_EXPORT void addBlockColumns(int32_t new_height); } } #endif diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index 90907b8a41..1f87f548ff 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -30,14 +30,12 @@ distribution. * @ingroup grp_modules */ #include "Export.h" -#include "Module.h" -#include "Types.h" -#include "BitArray.h" #include "DataDefs.h" #include "df/craft_material_class.h" -namespace df { +namespace df +{ struct item; struct plant_raw; struct creature_raw; @@ -56,28 +54,32 @@ namespace df { namespace DFHack { - struct t_matpair { + struct t_matpair + { int16_t mat_type; int32_t mat_index; t_matpair(int16_t type = -1, int32_t index = -1) - : mat_type(type), mat_index(index) {} + : mat_type(type), mat_index(index) + {} }; - struct DFHACK_EXPORT MaterialInfo { + struct DFHACK_EXPORT MaterialInfo + { static const int NUM_BUILTIN = 19; static const int GROUP_SIZE = 200; static const int CREATURE_BASE = NUM_BUILTIN; static const int FIGURE_BASE = NUM_BUILTIN + GROUP_SIZE; - static const int PLANT_BASE = NUM_BUILTIN + GROUP_SIZE*2; - static const int END_BASE = NUM_BUILTIN + GROUP_SIZE*3; + static const int PLANT_BASE = NUM_BUILTIN + GROUP_SIZE * 2; + static const int END_BASE = NUM_BUILTIN + GROUP_SIZE * 3; int16_t type; int32_t index; - df::material *material; + df::material* material; - enum Mode { + enum Mode + { None, Builtin, Inorganic, @@ -87,16 +89,16 @@ namespace DFHack Mode mode; int16_t subtype; - df::inorganic_raw *inorganic; - df::creature_raw *creature; - df::plant_raw *plant; + df::inorganic_raw* inorganic; + df::creature_raw* creature; + df::plant_raw* plant; - df::historical_figure *figure; + df::historical_figure* figure; public: MaterialInfo(int16_t type = -1, int32_t index = -1) { decode(type, index); } - MaterialInfo(const t_matpair &mp) { decode(mp.mat_type, mp.mat_index); } - template MaterialInfo(T *ptr) { decode(ptr); } + MaterialInfo(const t_matpair& mp) { decode(mp.mat_type, mp.mat_index); } + template MaterialInfo(T* ptr) { decode(ptr); } bool isValid() const { return material != NULL; } @@ -110,25 +112,27 @@ namespace DFHack bool isInorganicWildcard() const { return isAnyInorganic() && isBuiltin(); } bool decode(int16_t type, int32_t index = -1); - bool decode(df::item *item); - bool decode(const df::material_vec_ref &vr, int idx); - bool decode(const t_matpair &mp) { return decode(mp.mat_type, mp.mat_index); } + bool decode(df::item* item); + bool decode(const df::material_vec_ref& vr, int idx); + bool decode(const t_matpair& mp) { return decode(mp.mat_type, mp.mat_index); } - template bool decode(T *ptr) { + template bool decode(T* ptr) + { // Assume and exploit a certain naming convention return ptr ? decode(ptr->mat_type, ptr->mat_index) : decode(-1); } - bool find(const std::string &token); - bool find(const std::vector &tokens); + bool find(const std::string& token); + bool find(const std::vector& tokens); - bool findBuiltin(const std::string &token); - bool findInorganic(const std::string &token); - bool findPlant(const std::string &token, const std::string &subtoken); - bool findCreature(const std::string &token, const std::string &subtoken); + bool findBuiltin(const std::string& token); + bool findInorganic(const std::string& token); + bool findPlant(const std::string& token, const std::string& subtoken); + bool findCreature(const std::string& token, const std::string& subtoken); - bool findProduct(df::material *material, const std::string &name); - bool findProduct(const MaterialInfo &info, const std::string &name) { + bool findProduct(df::material* material, const std::string& name); + bool findProduct(const MaterialInfo& info, const std::string& name) + { return findProduct(info.material, name); } @@ -137,35 +141,38 @@ namespace DFHack bool isAnyCloth() const; - void getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &mask) const; - void getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &mask) const; - void getMatchBits(df::job_item_flags3 &ok, df::job_item_flags3 &mask) const; + void getMatchBits(df::job_item_flags1& ok, df::job_item_flags1& mask) const; + void getMatchBits(df::job_item_flags2& ok, df::job_item_flags2& mask) const; + void getMatchBits(df::job_item_flags3& ok, df::job_item_flags3& mask) const; df::craft_material_class getCraftClass(); - bool matches(const MaterialInfo &mat) const + bool matches(const MaterialInfo& mat) const { if (!mat.isValid()) return true; return (type == mat.type) && - (mat.index == -1 || index == mat.index); + (mat.index == -1 || index == mat.index); } - bool matches(const df::job_material_category &cat) const; - bool matches(const df::dfhack_material_category &cat) const; - bool matches(const df::job_item &jitem, - df::item_type itype = df::item_type::NONE) const; + bool matches(const df::job_material_category& cat) const; + bool matches(const df::dfhack_material_category& cat) const; + bool matches(const df::job_item& jitem, + df::item_type itype = df::item_type::NONE) const; }; - DFHACK_EXPORT bool parseJobMaterialCategory(df::job_material_category *cat, const std::string &token); - DFHACK_EXPORT bool parseJobMaterialCategory(df::dfhack_material_category *cat, const std::string &token); + DFHACK_EXPORT bool parseJobMaterialCategory(df::job_material_category* cat, const std::string& token); + DFHACK_EXPORT bool parseJobMaterialCategory(df::dfhack_material_category* cat, const std::string& token); - inline bool operator== (const MaterialInfo &a, const MaterialInfo &b) { + inline bool operator== (const MaterialInfo& a, const MaterialInfo& b) + { return a.type == b.type && a.index == b.index; } - inline bool operator!= (const MaterialInfo &a, const MaterialInfo &b) { + inline bool operator!= (const MaterialInfo& a, const MaterialInfo& b) + { return a.type != b.type || a.index != b.index; } - inline bool operator< (const MaterialInfo &a, const MaterialInfo &b) { + inline bool operator< (const MaterialInfo& a, const MaterialInfo& b) + { return a.type < b.type || (a.type == b.type && a.index < b.index); } @@ -347,38 +354,18 @@ namespace DFHack t_materialIndex mat_index; uint32_t flags; }; - /** - * The Materials module - * \ingroup grp_modules - * \ingroup grp_materials - */ - class DFHACK_EXPORT Materials : public Module + + + namespace Materials { - public: - Materials(); - ~Materials(); - bool Finish(); - - std::vector race; - std::vector raceEx; - std::vector color; - std::vector other; - std::vector alldesc; - - bool CopyInorganicMaterials (std::vector & inorganic); - bool CopyOrganicMaterials (std::vector & organic); - bool CopyWoodMaterials (std::vector & tree); - bool CopyPlantMaterials (std::vector & plant); - - bool ReadCreatureTypes (void); - bool ReadCreatureTypesEx (void); - bool ReadDescriptorColors(void); - bool ReadOthers (void); - - bool ReadAllMaterials(void); - - std::string getType(const t_material & mat); - std::string getDescription(const t_material & mat); - }; + /** + * The Materials module + * \ingroup grp_modules + * \ingroup grp_materials + */ + + std::string getType(const t_material& mat); + std::string getDescription(const t_material& mat); + } } #endif diff --git a/library/include/modules/Random.h b/library/include/modules/Random.h index 8ed703be79..b8e5de26bf 100644 --- a/library/include/modules/Random.h +++ b/library/include/modules/Random.h @@ -31,9 +31,6 @@ distribution. */ #include "Export.h" -#include "Module.h" -#include "Types.h" - #include "DataDefs.h" namespace DFHack diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index 3989460bb6..173cda6aa8 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -25,9 +25,6 @@ distribution. #pragma once #include "Export.h" -#include "Module.h" -#include "BitArray.h" -#include "ColorText.h" #include "Types.h" #include "DataDefs.h" @@ -35,6 +32,7 @@ distribution. #include "df/viewscreen.h" #include "df/graphic_viewportst.h" +#include "df/graphic_map_portst.h" #include #include @@ -203,6 +201,12 @@ namespace DFHack /// Retrieves one screen tile from the buffer DFHACK_EXPORT Pen readTile(int x, int y, bool map = false, int32_t * df::graphic_viewportst::*texpos_field = NULL); + /// Paint one world map tile with the given pen + DFHACK_EXPORT bool paintMapPortTile(const Pen &pen, int x, int y, int32_t * df::graphic_map_portst::*texpos_field = NULL); + + /// Retrieves one world map tile from the buffer + DFHACK_EXPORT Pen readMapPortTile(int x, int y, int32_t * df::graphic_map_portst::*texpos_field); + /// Paint a string onto the screen. Ignores ch and tile of pen. DFHACK_EXPORT bool paintString(const Pen &pen, int x, int y, const std::string &text, bool map = false); diff --git a/library/include/modules/Textures.h b/library/include/modules/Textures.h index b820c3332d..c2038c20b3 100644 --- a/library/include/modules/Textures.h +++ b/library/include/modules/Textures.h @@ -32,7 +32,7 @@ DFHACK_EXPORT TexposHandle loadTexture(SDL_Surface* surface, bool reserved = fal * Load tileset from image file. * Return vector of handles to obtain valid texposes. */ -DFHACK_EXPORT std::vector loadTileset(const std::string& file, +DFHACK_EXPORT std::vector loadTileset(const std::filesystem::path file, int tile_px_w = TILE_WIDTH_PX, int tile_px_h = TILE_HEIGHT_PX, bool reserved = false); diff --git a/library/include/modules/Translation.h b/library/include/modules/Translation.h index 2709dbf6fa..2860c3db87 100644 --- a/library/include/modules/Translation.h +++ b/library/include/modules/Translation.h @@ -31,9 +31,9 @@ distribution. */ #include "Export.h" -#include "Module.h" -#include "Types.h" #include "DataDefs.h" +#include "Types.h" +#include "df/language_name_type.h" namespace df { struct language_name; diff --git a/library/include/modules/World.h b/library/include/modules/World.h index b03170b5fa..a82ceef9a4 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -32,12 +32,10 @@ distribution. */ #include "Export.h" -#include "Module.h" #include "modules/Persistence.h" -#include - #include "DataDefs.h" + namespace df { struct tile_bitmask; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 15910b46f1..0dadc4fa9c 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -845,16 +845,26 @@ function dfhack.interpreter(prompt,hfile,env) return nil, 'not interactive' end - print("Type quit to exit interactive lua interpreter.") + local function print_keyword(pre, keyword, post) + dfhack.color(COLOR_RESET) + if pre ~= nil then dfhack.print(pre) end + dfhack.color(COLOR_YELLOW) + dfhack.print(keyword) + dfhack.color(COLOR_RESET) + if post ~= nil then print(post) end + end + print_keyword("Type ", "quit", " to exit interactive lua interpreter.") if print_banner then - print("Shortcuts:\n".. - " '= foo' => '_1,_2,... = foo'\n".. - " '! foo' => 'print(foo)'\n".. - " '~ foo' => 'printall(foo)'\n".. - " '^ foo' => 'printall_recurse(foo)'\n".. - " '@ foo' => 'printall_ipairs(foo)'\n".. - "All of these save the first result as '_'.") + print("Shortcuts:") + print_keyword(" '", "= foo", "' => '_1,_2,... = foo'") + print_keyword(" '", "! foo", "' => 'print(foo)'") + print_keyword(" '", "~ foo", "' => 'printall(foo)'") + print_keyword(" '", "^ foo", "' => 'printall_recurse(foo)'") + print_keyword(" '", "@ foo", "' => 'printall_ipairs(foo)'") + print_keyword("All of these save the first result as '", "_", "'.") + print("These keywords refer to the currently-selected object in the game:") + print_keyword(" ", "unit item plant building bld job workshop_job wsjob screen scr", "") print_banner = false end diff --git a/library/lua/dfhack/buildings.lua b/library/lua/dfhack/buildings.lua index 7fff00dcf2..ae4d624360 100644 --- a/library/lua/dfhack/buildings.lua +++ b/library/lua/dfhack/buildings.lua @@ -130,7 +130,6 @@ local building_inputs = { vector_id=df.job_item_vector_id.PIPE_SECTION } }, - [df.building_type.Construction] = { { flags2={ building_material=true, non_economic=true } } }, [df.building_type.Hatch] = { { item_type=df.item_type.HATCH_COVER, @@ -353,6 +352,27 @@ local siegeengine_input = { quantity=3 } }, + [df.siegeengine_type.BoltThrower] = { + { + item_type=df.item_type.BOLT_THROWER_PARTS, + vector_id=df.job_item_vector_id.BOLT_THROWER_PARTS, + }, + { + flags1={ empty=true }, + item_type=df.item_type.BIN, + vector_id=df.job_item_vector_id.BIN, + }, + { + name='mechanism', + item_type=df.item_type.TRAPPARTS, + vector_id=df.job_item_vector_id.TRAPPARTS, + }, + { + name='chain', + item_type=df.item_type.CHAIN, + vector_id=df.job_item_vector_id.CHAIN + }, + }, } --[[ Functions for lookup in tables. ]] @@ -380,6 +400,11 @@ local function get_inputs_by_type(type,subtype,custom) return trap_inputs[subtype] elseif type == df.building_type.SiegeEngine then return siegeengine_input[subtype] + elseif type == df.building_type.Construction then + if subtype == df.construction_type.ReinforcedWall then + return { { flags2={ building_material=true, non_economic=true }, quantity=2 }, { flags3={ metal=true }, item_type=df.item_type.BAR, vector_id=df.job_item_vector_id.BAR } } + end + return { { flags2={ building_material=true, non_economic=true } } } else return building_inputs[type] end diff --git a/library/lua/gui/textures.lua b/library/lua/gui/textures.lua index 44d4f0de30..b97a10e439 100644 --- a/library/lua/gui/textures.lua +++ b/library/lua/gui/textures.lua @@ -8,16 +8,16 @@ local _ENV = mkmodule('gui.textures') -- Use these handles if you need to get dfhack standard textures. ---@type table local texpos_handles = { - 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, true), - 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, true), - 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, true), - 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, true), - 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, true), + green_pin = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/green-pin.png', 8, 12, true), + red_pin = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/red-pin.png', 8, 12, true), + icons = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/icons.png', 8, 12, true), + on_off = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/on-off.png', 8, 12, true), + control_panel = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/control-panel.png', 8, 12, true), + border_thin = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/border-thin.png', 8, 12, true), + border_medium = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/border-medium.png', 8, 12, true), + border_bold = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/border-bold.png', 8, 12, true), + border_panel = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/border-panel.png', 8, 12, true), + border_window = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/border-window.png', 8, 12, true), } -- Get valid texpos for preloaded texture in tileset diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index e7870f88e8..726b52003a 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -16,8 +16,9 @@ Label = require('gui.widgets.labels.label') Scrollbar = require('gui.widgets.scrollbar') WrappedLabel = require('gui.widgets.labels.wrapped_label') TooltipLabel = require('gui.widgets.labels.tooltip_label') -HelpButton = require('gui.widgets.buttons.help_button') ConfigureButton = require('gui.widgets.buttons.configure_button') +HelpButton = require('gui.widgets.buttons.help_button') +RadioButton = require('gui.widgets.buttons.radio_button') BannerPanel = require('gui.widgets.containers.banner_panel') TextButton = require('gui.widgets.buttons.text_button') CycleHotkeyLabel = require('gui.widgets.labels.cycle_hotkey_label') diff --git a/library/lua/gui/widgets/buttons/configure_button.lua b/library/lua/gui/widgets/buttons/configure_button.lua index b93b1e18b2..7d865b27ad 100644 --- a/library/lua/gui/widgets/buttons/configure_button.lua +++ b/library/lua/gui/widgets/buttons/configure_button.lua @@ -1,3 +1,5 @@ +-- A 3x1 tile button with a gear symbol on it. Clicking on it will run a callback + local textures = require('gui.textures') local Panel = require('gui.widgets.containers.panel') local Label = require('gui.widgets.labels.label') @@ -6,10 +8,10 @@ local to_pen = dfhack.pen.parse local button_pen_left = to_pen{fg=COLOR_CYAN, tile=curry(textures.tp_control_panel, 7) or nil, ch=string.byte('[')} +local button_pen_center = to_pen{ + tile=curry(textures.tp_control_panel, 10) or nil, ch=15} -- gear/masterwork symbol local button_pen_right = to_pen{fg=COLOR_CYAN, tile=curry(textures.tp_control_panel, 8) or nil, ch=string.byte(']')} -local configure_pen_center = to_pen{ - tile=curry(textures.tp_control_panel, 10) or nil, ch=15} -- gear/masterwork symbol --------------------- -- ConfigureButton -- @@ -17,6 +19,9 @@ local configure_pen_center = to_pen{ ---@class widgets.ConfigureButton.attrs: widgets.Panel.attrs ---@field on_click? function +---@field pen_left dfhack.pen|fun(): dfhack.pen +---@field pen_center dfhack.pen|fun(): dfhack.pen +---@field pen_right dfhack.pen|fun(): dfhack.pen ---@class widgets.ConfigureButton.attrs.partial: widgets.ConfigureButton.attrs @@ -27,27 +32,40 @@ local configure_pen_center = to_pen{ ConfigureButton = defclass(ConfigureButton, Panel) ConfigureButton.ATTRS{ + frame={t=0, l=0, w=3, h=1}, on_click=DEFAULT_NIL, + pen_left=button_pen_left, + pen_center=button_pen_center, + pen_right=button_pen_right, } -function ConfigureButton:preinit(init_table) - init_table.frame = init_table.frame or {} - init_table.frame.h = init_table.frame.h or 1 - init_table.frame.w = init_table.frame.w or 3 -end - function ConfigureButton:init() + self.frame.h = self.frame.h or 1 + self.frame.w = self.frame.w or 3 + self:addviews{ Label{ + view_id='label', frame={t=0, l=0, w=3, h=1}, text={ - {tile=button_pen_left}, - {tile=configure_pen_center}, - {tile=button_pen_right}, + {tile=self.pen_left}, + {tile=self.pen_center}, + {tile=self.pen_right}, }, on_click=self.on_click, }, } end +function ConfigureButton:postinit() + local l = self.subviews.label + + l.on_click = self.on_click + l.pen_left = self.pen_left + l.pen_center = self.pen_center + l.pen_right = self.pen_right + + l:setText({{tile=self.pen_left}, {tile=self.pen_center}, {tile=self.pen_right}}) +end + return ConfigureButton diff --git a/library/lua/gui/widgets/buttons/help_button.lua b/library/lua/gui/widgets/buttons/help_button.lua index 9f9b7dc989..9fa45f7a70 100644 --- a/library/lua/gui/widgets/buttons/help_button.lua +++ b/library/lua/gui/widgets/buttons/help_button.lua @@ -1,53 +1,35 @@ +-- A 3x1 tile button with a question mark on it. Clicking on it will show help text for a command + local textures = require('gui.textures') -local Panel = require('gui.widgets.containers.panel') -local Label = require('gui.widgets.labels.label') +local ConfigureButton = require('gui.widgets.buttons.configure_button') -local to_pen = dfhack.pen.parse +local help_pen_center = dfhack.pen.parse{ + tile=curry(textures.tp_control_panel, 9) or nil, ch=string.byte('?')} ---------------- -- HelpButton -- ---------------- ----@class widgets.HelpButton.attrs: widgets.Panel.attrs +---@class widgets.HelpButton.attrs: widgets.ConfigureButton.attrs ---@field command? string ---@class widgets.HelpButton.attrs.partial: widgets.HelpButton.attrs ----@class widgets.HelpButton: widgets.Panel, widgets.HelpButton.attrs ----@field super widgets.Panel +---@class widgets.HelpButton: widgets.ConfigureButton, widgets.HelpButton.attrs +---@field super widgets.ConfigureButton ---@field ATTRS widgets.HelpButton.attrs|fun(attributes: widgets.HelpButton.attrs.partial) ---@overload fun(init_table: widgets.HelpButton.attrs.partial): self -HelpButton = defclass(HelpButton, Panel) +HelpButton = defclass(HelpButton, ConfigureButton) HelpButton.ATTRS{ frame={t=0, r=1, w=3, h=1}, command=DEFAULT_NIL, + pen_center=help_pen_center, } -local button_pen_left = to_pen{fg=COLOR_CYAN, - tile=curry(textures.tp_control_panel, 7) or nil, ch=string.byte('[')} -local button_pen_right = to_pen{fg=COLOR_CYAN, - tile=curry(textures.tp_control_panel, 8) or nil, ch=string.byte(']')} -local help_pen_center = to_pen{ - tile=curry(textures.tp_control_panel, 9) or nil, ch=string.byte('?')} - function HelpButton:init() - self.frame.w = self.frame.w or 3 - self.frame.h = self.frame.h or 1 - local command = self.command .. ' ' - - self:addviews{ - Label{ - frame={t=0, l=0, w=3, h=1}, - text={ - {tile=button_pen_left}, - {tile=help_pen_center}, - {tile=button_pen_right}, - }, - on_click=function() dfhack.run_command('gui/launcher', command) end, - }, - } + self.on_click = function() dfhack.run_command('gui/launcher', command) end end return HelpButton diff --git a/library/lua/gui/widgets/buttons/radio_button.lua b/library/lua/gui/widgets/buttons/radio_button.lua new file mode 100644 index 0000000000..d27d54edc3 --- /dev/null +++ b/library/lua/gui/widgets/buttons/radio_button.lua @@ -0,0 +1,59 @@ +-- A 3x1 tile button that toggles state when clicked + +local textures = require('gui.textures') +local ConfigureButton = require('gui.widgets.buttons.configure_button') + +local to_pen = dfhack.pen.parse + +local enabled_pen_left = to_pen{fg=COLOR_CYAN, + tile=curry(textures.tp_control_panel, 1) or nil, ch=string.byte('[')} +local enabled_pen_center = to_pen{fg=COLOR_LIGHTGREEN, + tile=curry(textures.tp_control_panel, 2) or nil, ch=251} -- check mark +local enabled_pen_right = to_pen{fg=COLOR_CYAN, + tile=curry(textures.tp_control_panel, 3) or nil, ch=string.byte(']')} +local disabled_pen_left = to_pen{fg=COLOR_CYAN, + tile=curry(textures.tp_control_panel, 4) or nil, ch=string.byte('[')} +local disabled_pen_center = to_pen{fg=COLOR_RED, + tile=curry(textures.tp_control_panel, 5) or nil, ch=string.byte('x')} +local disabled_pen_right = to_pen{fg=COLOR_CYAN, + tile=curry(textures.tp_control_panel, 6) or nil, ch=string.byte(']')} + +----------------- +-- RadioButton -- +----------------- + +---@class widgets.RadioButton.attrs: widgets.ConfigureButton.attrs +---@field initial_state boolean +---@field on_change? fun(val: boolean) + +---@class widgets.RadioButton.attrs.partial: widgets.RadioButton.attrs + +---@class widgets.RadioButton: widgets.ConfigureButton, widgets.RadioButton.attrs +---@field super widgets.ConfigureButton +---@field ATTRS widgets.RadioButton.attrs|fun(attributes: widgets.RadioButton.attrs.partial) +---@overload fun(init_table: widgets.RadioButton.attrs.partial): self +RadioButton = defclass(RadioButton, ConfigureButton) + +RadioButton.ATTRS{ + initial_state=true, + on_change=DEFAULT_NIL, +} + +function RadioButton:setState(val) + self.toggle_state = not not val + + if self.on_change then + self.on_change(self.toggle_state) + end +end + +function RadioButton:init() + self.on_click = function() self:setState(not self.toggle_state) end + self.pen_left = function() return self.toggle_state and enabled_pen_left or disabled_pen_left end + self.pen_center = function() return self.toggle_state and enabled_pen_center or disabled_pen_center end + self.pen_right = function() return self.toggle_state and enabled_pen_right or disabled_pen_right end + + self:setState(self.initial_state) +end + +return RadioButton diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 0bd64d29cc..5af55f5697 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -5,8 +5,8 @@ local _ENV = mkmodule('helpdb') local argparse = require('argparse') -- paths -local RENDERED_PATH = 'hack/docs/docs/tools/' -local TAG_DEFINITIONS = 'hack/docs/docs/Tags.txt' +local RENDERED_PATH = dfhack.getHackPath() .. '/docs/docs/tools/' +local TAG_DEFINITIONS = dfhack.getHackPath() .. '/docs/docs/Tags.txt' -- used when reading help text embedded in script sources local SCRIPT_DOC_BEGIN = '[====[' diff --git a/library/lua/script-manager.lua b/library/lua/script-manager.lua index 74a31fd1d9..cb4abf1f53 100644 --- a/library/lua/script-manager.lua +++ b/library/lua/script-manager.lua @@ -152,6 +152,7 @@ function get_mod_paths(installed_subdir, active_subdir) -- if a world is loaded, process active mods first, and lock to active version if dfhack.isWorldLoaded() then for _,path in ipairs(df.global.world.object_loader.object_load_order_src_dir) do + path = dfhack.filesystem.getBaseDir() .. path -- skip vanilla "mods" if not path:startswith(INSTALLED_MODS_PATH) then goto continue end local id = get_mod_id_and_version(path) @@ -245,7 +246,7 @@ function getModSourcePath(mod_id) end function getModStatePath(mod_id) - local path = ('dfhack-config/mods/%s/'):format(mod_id) + local path = (dfhack.getConfigPath() + ('/mods/%s/')):format(mod_id) if not dfhack.filesystem.mkdir_recursive(path) then error(('failed to create mod state directory: "%s"'):format(path)) end diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index b7aeb55996..ddedbbc1b6 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -29,7 +29,6 @@ distribution. #include "MemAccess.h" #include "Types.h" #include "Error.h" -#include "ModuleFactory.h" #include "Core.h" #include "TileTypes.h" #include "MiscUtils.h" @@ -615,6 +614,23 @@ bool Buildings::getCorrectSize(df::coord2d &size, df::coord2d ¢er, return false; case SiegeEngine: + { + using namespace df::enums::siegeengine_type; + + switch((df::siegeengine_type)subtype) + { + case df::siegeengine_type::BoltThrower: + size = df::coord2d(1, 1); + center = df::coord2d(0, 0); + break; + default: + size = df::coord2d(3,3); + center = df::coord2d(1,1); + break; + } + return false; + } + case Windmill: case Wagon: size = df::coord2d(3,3); diff --git a/library/modules/Burrows.cpp b/library/modules/Burrows.cpp index 3c641e79b7..17ab0389d4 100644 --- a/library/modules/Burrows.cpp +++ b/library/modules/Burrows.cpp @@ -52,6 +52,13 @@ using namespace df::enums; using df::global::world; using df::global::plotinfo; +std::string Burrows::getName(df::burrow* burrow) +{ + CHECK_NULL_POINTER(burrow); + return burrow->name.empty() ? fmt::format("Burrow {}", burrow->id + 1) : burrow->name; +} + + df::burrow *Burrows::findByName(std::string name, bool ignore_final_plus) { auto &vec = df::burrow::get_vector(); @@ -99,8 +106,6 @@ bool Burrows::isAssignedUnit(df::burrow *burrow, df::unit *unit) void Burrows::setAssignedUnit(df::burrow *burrow, df::unit *unit, bool enable) { - using df::global::plotinfo; - CHECK_NULL_POINTER(unit); CHECK_NULL_POINTER(burrow); diff --git a/library/modules/DFSDL.cpp b/library/modules/DFSDL.cpp index 536d1a5881..f23cde5511 100644 --- a/library/modules/DFSDL.cpp +++ b/library/modules/DFSDL.cpp @@ -1,3 +1,4 @@ +#include "Error.h" #include "Internal.h" #include "modules/DFSDL.h" @@ -6,6 +7,9 @@ #include "PluginManager.h" #include +#include + +#include #ifdef WIN32 # include @@ -60,6 +64,12 @@ SDL_Surface* (*g_SDL_CreateRGBSurfaceWithFormat)(uint32_t flags, int width, int int (*g_SDL_ShowSimpleMessageBox)(uint32_t flags, const char *title, const char *message, SDL_Window *window) = nullptr; char* (*g_SDL_GetPrefPath)(const char* org, const char* app) = nullptr; char* (*g_SDL_GetBasePath)() = nullptr; +uint32_t (*g_SDL_GetMouseState)(int* x, int* y) = nullptr; +void (*g_SDL_RenderWindowToLogical)(SDL_Renderer* renderer, int windowX, int windowY, float* logicalX, float* logicalY); +void (*g_SDL_RenderLogicalToWindow)(SDL_Renderer* renderer, float logicalX, float logicalY, int* windowX, int* windowY); + +SDL_Keycode (*g_SDL_GetKeyFromName)(const char* name) = nullptr; +const char* (*g_SDL_GetKeyName)(SDL_Keycode key) = nullptr; bool DFSDL::init(color_ostream &out) { for (auto &lib_str : SDL_LIBS) { @@ -105,6 +115,11 @@ bool DFSDL::init(color_ostream &out) { bind(g_sdl_handle, SDL_ShowSimpleMessageBox); bind(g_sdl_handle, SDL_GetPrefPath); bind(g_sdl_handle, SDL_GetBasePath); + bind(g_sdl_handle, SDL_GetKeyFromName); + bind(g_sdl_handle, SDL_GetKeyName); + bind(g_sdl_handle, SDL_GetMouseState); + bind(g_sdl_handle, SDL_RenderWindowToLogical); + bind(g_sdl_handle, SDL_RenderLogicalToWindow); #undef bind DEBUG(dfsdl,out).print("sdl successfully loaded\n"); @@ -189,12 +204,36 @@ char* DFSDL::DFSDL_GetBasePath() return g_SDL_GetBasePath(); } +uint32_t DFSDL::DFSDL_GetMouseState(int* x, int* y) { + return g_SDL_GetMouseState(x, y); +} + +void DFSDL::DFSDL_RenderWindowToLogical(SDL_Renderer *renderer, int windowX, int windowY, float *logicalX, float *logicalY) { + g_SDL_RenderWindowToLogical(renderer, windowX, windowY, logicalX, logicalY); +} + +void DFSDL::DFSDL_RenderLogicalToWindow(SDL_Renderer *renderer, float logicalX, float logicalY, int *windowX, int *windowY) { + g_SDL_RenderLogicalToWindow(renderer, logicalX, logicalY, windowX, windowY); +} + int DFSDL::DFSDL_ShowSimpleMessageBox(uint32_t flags, const char *title, const char *message, SDL_Window *window) { if (!g_SDL_ShowSimpleMessageBox) return -1; return g_SDL_ShowSimpleMessageBox(flags, title, message, window); } +SDL_Keycode DFSDL::DFSDL_GetKeyFromName(const char* name) { + if (!g_SDL_GetKeyFromName) + return SDLK_UNKNOWN; + return g_SDL_GetKeyFromName(name); +} + +const char* DFSDL::DFSDL_GetKeyName(SDL_Keycode key) { + if (!g_SDL_GetKeyName) + return ""; + return g_SDL_GetKeyName(key); +} + // convert tabs to spaces so they don't get converted to '?' static char * tabs_to_spaces(char *str) { for (char *c = str; *c; ++c) { @@ -265,3 +304,25 @@ DFHACK_EXPORT bool DFHack::setClipboardTextCp437Multiline(string text) { } return 0 == DFHack::DFSDL::DFSDL_SetClipboardText(str.str().c_str()); } + +// Queue to run callbacks on the render thread. +// Semantics loosely based on SDL3's SDL_RunOnMainThread +static std::recursive_mutex render_cb_lock; +static std::vector> render_cb_queue; + +DFHACK_EXPORT void DFHack::runOnRenderThread(std::function cb) { + std::lock_guard l(render_cb_lock); + render_cb_queue.push_back(std::move(cb)); +} + +DFHACK_EXPORT void DFHack::runRenderThreadCallbacks() { + static decltype(render_cb_queue) local_queue; + { + std::lock_guard l(render_cb_lock); + std::swap(local_queue, render_cb_queue); + } + for (auto& cb : local_queue) { + cb(); + } + local_queue.clear(); +} diff --git a/library/modules/DFSteam.cpp b/library/modules/DFSteam.cpp index b5892bbb16..9c61b2a7fc 100644 --- a/library/modules/DFSteam.cpp +++ b/library/modules/DFSteam.cpp @@ -1,11 +1,30 @@ -#include "Internal.h" - #include "modules/DFSteam.h" #include "Debug.h" #include "PluginManager.h" +#include "ColorText.h" +#include "Core.h" + +#include +#include +#include +#include +#include + #include "df/gamest.h" +#include + +#ifdef WIN32 +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#endif namespace DFHack { @@ -35,17 +54,20 @@ bool (*g_SteamAPI_RestartAppIfNecessary)(uint32_t unOwnAppID) = nullptr; void* (*g_SteamInternal_FindOrCreateUserInterface)(int, const char*) = nullptr; bool (*g_SteamAPI_ISteamApps_BIsAppInstalled)(void *iSteamApps, uint32_t appID) = nullptr; -static void bind_all(color_ostream& out, DFLibrary* handle) { -#define bind(name) \ - if (!handle) { \ - g_##name = nullptr; \ - } else { \ - g_##name = (decltype(g_##name))LookupPlugin(handle, #name); \ - if (!g_##name) { \ - WARN(dfsteam, out).print("steam library function not found: " #name "\n"); \ - } \ +template +static void bind_(color_ostream& out, DFLibrary* handle, const char* name, Ptr& func_ptr) { + if (!handle) { + func_ptr = nullptr; + } else { + func_ptr = (Ptr)LookupPlugin(handle, name); + if (!func_ptr) { + WARN(dfsteam, out).print("steam library function not found: {}\n", name); } + } +} +static void bind_all(color_ostream& out, DFLibrary* handle) { +#define bind(name) bind_(out, handle, #name, g_##name) bind(SteamAPI_Init); bind(SteamAPI_Shutdown); bind(SteamAPI_GetHSteamUser); @@ -56,6 +78,16 @@ static void bind_all(color_ostream& out, DFLibrary* handle) { #undef bind } +static void unbind_all() +{ + g_SteamAPI_Init = nullptr; + g_SteamAPI_Shutdown = nullptr; + g_SteamAPI_GetHSteamUser = nullptr; + g_SteamInternal_FindOrCreateUserInterface = nullptr; + g_SteamAPI_RestartAppIfNecessary = nullptr; + g_SteamAPI_ISteamApps_BIsAppInstalled = nullptr; +} + bool DFSteam::init(color_ostream& out) { char *steam_client_launch = getenv("SteamClientLaunch"); if (!steam_client_launch || strncmp(steam_client_launch, "1", 2) != 0) { @@ -84,7 +116,7 @@ bool DFSteam::init(color_ostream& out) { return true; } -void DFSteam::cleanup(color_ostream& out) { +void DFSteam::cleanup() { if (!g_steam_handle) return; @@ -94,15 +126,12 @@ void DFSteam::cleanup(color_ostream& out) { ClosePlugin(g_steam_handle); g_steam_handle = nullptr; - bind_all(out, nullptr); + unbind_all(); g_steam_initialized = false; } #ifdef WIN32 -#include -#include -#include static bool is_running_on_wine() { typedef const char* (CDECL wine_get_version)(void); @@ -157,13 +186,13 @@ static bool launchDFHack(color_ostream& out) { si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); - static LPCWSTR procname = L"hack/launchdf.exe"; + auto procpath = Core::getInstance().getHackPath() / "launchdf.exe"; static const char * env = "\0"; // note that the environment must be explicitly zeroed out and not NULL, // otherwise the launched process will inherit this process's environment, // and the Steam API in the launchdf process will think it is in DF's context. - BOOL res = CreateProcessW(procname, + BOOL res = CreateProcessW(procpath.wstring().c_str(), NULL, NULL, NULL, FALSE, 0, (LPVOID)env, NULL, &si, &pi); return !!res; @@ -179,8 +208,8 @@ static bool findProcess(color_ostream& out, std::string name, pid_t &pid) { command += name; FILE *cmd_pipe = popen(command.c_str(), "r"); if (!cmd_pipe) { - WARN(dfsteam, out).print("failed to exec '%s' (error: %d)\n", - command.c_str(), errno); + WARN(dfsteam, out).print("failed to exec '{}' (error: {})\n", + command, errno); return false; } @@ -204,13 +233,14 @@ static bool launchDFHack(color_ostream& out) { pid = fork(); if (pid == -1) { - WARN(dfsteam, out).print("failed to fork (error: %d)\n", errno); + WARN(dfsteam, out).print("failed to fork (error: {})\n", errno); return false; } else if (pid == 0) { // child process - static const char * command = "hack/launchdf"; + auto procpath = Core::getInstance().getHackPath() / "launchdf"; + auto command = procpath.string(); unsetenv("SteamAppId"); - execl(command, command, NULL); + execl(command.c_str(), command.c_str(), NULL); _exit(EXIT_FAILURE); } @@ -248,5 +278,5 @@ void DFSteam::launchSteamDFHackIfNecessary(color_ostream& out) { } bool ret = launchDFHack(out); - DEBUG(dfsteam, out).print("launching DFHack via Steam: %s\n", ret ? "successful" : "unsuccessful"); + DEBUG(dfsteam, out).print("launching DFHack via Steam: {}\n", ret ? "successful" : "unsuccessful"); } diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 5a24f1174a..3c926645a2 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -2,6 +2,7 @@ #include "Console.h" #include "Debug.h" #include "VTableInterpose.h" +#include "MemAccess.h" #include "modules/Buildings.h" #include "modules/Constructions.h" @@ -70,7 +71,10 @@ static int32_t eventLastTick[EventType::EVENT_MAX]; static const int32_t ticksPerYear = 403200; void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler) { - DEBUG(log).print("registering handler %p from plugin %s for event %d\n", handler.eventHandler, !handler.plugin ? "" : handler.plugin->getName().c_str(), e); + DEBUG(log).print("registering handler {} from plugin {} for event {}\n", + reinterpret_cast(handler.eventHandler), + handler.plugin ? handler.plugin->getName() : "", + static_cast(e)); handlers[e].insert(pair(handler.plugin, handler)); } @@ -86,7 +90,9 @@ int32_t DFHack::EventManager::registerTick(EventHandler handler, int32_t when, b } handler.freq = when; tickQueue.insert(pair(handler.freq, handler)); - DEBUG(log).print("registering handler %p from plugin %s for event TICK\n", handler.eventHandler, !handler.plugin ? "" : handler.plugin->getName().c_str()); + DEBUG(log).print("registering handler {} from plugin {} for event TICK\n", + reinterpret_cast(handler.eventHandler), + handler.plugin ? handler.plugin->getName() : ""); handlers[EventType::TICK].insert(pair(handler.plugin,handler)); return when; } @@ -112,7 +118,10 @@ void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handl i++; continue; } - DEBUG(log).print("unregistering handler %p from plugin %s for event %d\n", handler.eventHandler, !handler.plugin ? "" : handler.plugin->getName().c_str(), e); + DEBUG(log).print("unregistering handler {} from plugin {} for event {}\n", + reinterpret_cast(handler.eventHandler), + handler.plugin ? handler.plugin->getName() : "", + static_cast(e)); i = handlers[e].erase(i); if ( e == EventType::TICK ) removeFromTickQueue(handler); @@ -120,7 +129,8 @@ void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handl } void DFHack::EventManager::unregisterAll(Plugin* plugin) { - DEBUG(log).print("unregistering all handlers for plugin %s\n", !plugin ? "" : plugin->getName().c_str()); + DEBUG(log).print("unregistering all handlers for plugin {}\n", + plugin ? plugin->getName() : ""); for ( auto i = handlers[EventType::TICK].find(plugin); i != handlers[EventType::TICK].end(); i++ ) { if ( (*i).first != plugin ) break; @@ -407,7 +417,7 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { CoreSuspender suspender; int32_t tick = df::global::world->frame_counter; - TRACE(log,out).print("processing events at tick %d\n", tick); + TRACE(log,out).print("processing events at tick {}\n", tick); auto &core = Core::getInstance(); auto &counters = core.perf_counters; @@ -598,13 +608,13 @@ static void manageJobCompletedEvent(color_ostream& out) { df::job& job1 = *(*j).second; out.print("new job\n" - " location : 0x%X\n" - " id : %d\n" - " type : %d %s\n" - " working : %d\n" - " completion_timer : %d\n" - " workerID : %d\n" - " time : %d -> %d\n" + " location : {:#X}\n" + " id : {}\n" + " type : {} {}\n" + " working : {}\n" + " completion_timer : {}\n" + " workerID : {}\n" + " time : {} -> {}\n" "\n", job1.list_link->item, job1.id, job1.job_type, ENUM_ATTR(job_type, caption, job1.job_type), job1.flags.bits.working, job1.completion_timer, getWorkerID(&job1), tick0, tick1); } for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) { @@ -612,13 +622,13 @@ static void manageJobCompletedEvent(color_ostream& out) { auto j = nowJobs.find((*i).first); if ( j == nowJobs.end() ) { out.print("job deallocated\n" - " location : 0x%X\n" - " id : %d\n" - " type : %d %s\n" - " working : %d\n" - " completion_timer : %d\n" - " workerID : %d\n" - " time : %d -> %d\n" + " location : {:#X}\n" + " id : {}\n" + " type : {} {}\n" + " working : {}\n" + " completion_timer : {}\n" + " workerID : {}\n" + " time : {} -> {}\n" ,job0.list_link == NULL ? 0 : job0.list_link->item, job0.id, job0.job_type, ENUM_ATTR(job_type, caption, job0.job_type), job0.flags.bits.working, job0.completion_timer, getWorkerID(&job0), tick0, tick1); continue; } @@ -630,14 +640,14 @@ static void manageJobCompletedEvent(color_ostream& out) { continue; out.print("job change\n" - " location : 0x%X -> 0x%X\n" - " id : %d -> %d\n" - " type : %d -> %d\n" - " type : %s -> %s\n" - " working : %d -> %d\n" - " completion timer : %d -> %d\n" - " workerID : %d -> %d\n" - " time : %d -> %d\n" + " location : {:#X} -> {:#X}\n" + " id : {} -> {}\n" + " type : {} -> {}\n" + " type : {} -> {}\n" + " working : {} -> {}\n" + " completion timer : {} -> {}\n" + " workerID : {} -> {}\n" + " time : {} -> {}\n" "\n", job0.list_link->item, job1.list_link->item, job0.id, job1.id, @@ -1228,7 +1238,7 @@ static void manageUnitAttackEvent(color_ostream& out) { if ( reportStr.find("severed part") ) continue; if ( Once::doOnce("EventManager neither wound") ) { - out.print("%s, %d: neither wound: %s\n", __FILE__, __LINE__, reportStr.c_str()); + out.print("{}, {}: neither wound: {}\n", __FILE__, __LINE__, reportStr.c_str()); } } } @@ -1351,9 +1361,8 @@ static InteractionData getAttacker(color_ostream& out, df::report* attackEvent, //if trying attack-defend pair and it fails to find attacker, try defend only InteractionData result = /*(InteractionData)*/ { std::string(), std::string(), -1, -1, -1, -1 }; if ( attackers.size() > 1 ) { -//out.print("%s,%d\n",__FILE__,__LINE__); if ( Once::doOnce("EventManager interaction ambiguous attacker") ) { - out.print("%s,%d: ambiguous attacker on report\n \'%s\'\n '%s'\n", __FILE__, __LINE__, attackEvent ? attackEvent->text.c_str() : "", defendEvent ? defendEvent->text.c_str() : ""); + out.print("{},{}: ambiguous attacker on report\n \'{}\'\n \'{}\'\n", __FILE__, __LINE__, attackEvent ? attackEvent->text : "", defendEvent ? defendEvent->text : ""); } } else if ( attackers.empty() ) { //out.print("%s,%d\n",__FILE__,__LINE__); @@ -1367,7 +1376,7 @@ static InteractionData getAttacker(color_ostream& out, df::report* attackEvent, result.defender = defenders[0]->id; if ( defenders.size() > 1 ) { if ( Once::doOnce("EventManager interaction ambiguous defender") ) { - out.print("%s,%d: ambiguous defender: shouldn't happen. On report\n \'%s\'\n '%s'\n", __FILE__, __LINE__, attackEvent ? attackEvent->text.c_str() : "", defendEvent ? defendEvent->text.c_str() : ""); + out.print("{}, {}: ambiguous defender: shouldn't happen. On report\n \'{}\'\n \'{}\'\n", __FILE__, __LINE__, attackEvent ? attackEvent->text : "", defendEvent ? defendEvent->text : ""); } } result.attackVerb = attackVerb; @@ -1394,7 +1403,7 @@ static vector gatherRelevantUnits(color_ostream& out, df::report* r1, vector& units = reportToRelevantUnits[report->id]; if ( units.size() > 2 ) { if ( Once::doOnce("EventManager interaction too many relevant units") ) { - out.print("%s,%d: too many relevant units. On report\n \'%s\'\n", __FILE__, __LINE__, report->text.c_str()); + out.print("{},{}: too many relevant units. On report\n \'{}\'\n", __FILE__, __LINE__, report->text); } } for (int & unit_id : units) diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index 7a6b09a500..6d49c6a3b7 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -154,17 +154,31 @@ bool Filesystem::stat (std::filesystem::path path, std::filesystem::file_status bool Filesystem::exists (std::filesystem::path path) noexcept { - return std::filesystem::exists(path); + std::error_code ec; + auto r = std::filesystem::exists(path, ec); + if (ec) + return false; + return r; } bool Filesystem::isfile(std::filesystem::path path) noexcept { - return std::filesystem::exists(path) && std::filesystem::is_regular_file(path); + std::error_code ec; + // is_regular_file() also checks for existence. + auto r = std::filesystem::is_regular_file(path, ec); + if (ec) + return false; + return r; } bool Filesystem::isdir (std::filesystem::path path) noexcept { - return std::filesystem::exists(path) && std::filesystem::is_directory(path); + std::error_code ec; + // is_directory() also checks for existence. + auto r = std::filesystem::is_directory(path, ec); + if (ec) + return false; + return r; } std::time_t Filesystem::mtime (std::filesystem::path path) noexcept diff --git a/library/modules/Graphic.cpp b/library/modules/Graphic.cpp deleted file mode 100644 index b55ee83ed4..0000000000 --- a/library/modules/Graphic.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mr�zek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - - -#include "Internal.h" - -#include -#include -#include -#include -#include -using namespace std; - -#include "modules/Graphic.h" -#include "Error.h" -#include "VersionInfo.h" -#include "MemAccess.h" -#include "MiscUtils.h" -#include "ModuleFactory.h" -#include "Core.h" - -using namespace DFHack; - -std::unique_ptr DFHack::createGraphic() -{ - return std::make_unique(); -} - -bool Graphic::Register(DFTileSurface* (*func)(int,int)) -{ - funcs.push_back(func); - return true; -} - -bool Graphic::Unregister(DFTileSurface* (*func)(int,int)) -{ - if ( funcs.empty() ) return false; - - vector::iterator it = funcs.begin(); - while ( it != funcs.end() ) - { - if ( *it == func ) - { - funcs.erase(it); - return true; - } - it++; - } - - return false; -} - -// This will return first DFTileSurface it can get (or NULL if theres none) -DFTileSurface* Graphic::Call(int x, int y) -{ - if ( funcs.empty() ) return NULL; - - DFTileSurface* temp = NULL; - - vector::iterator it = funcs.begin(); - while ( it != funcs.end() ) - { - temp = (*it)(x,y); - if ( temp != NULL ) - { - return temp; - } - it++; - } - - return NULL; -} diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index dedcc6cdbc..e7c8233ba1 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -30,7 +30,6 @@ distribution. #include "VersionInfo.h" #include "Types.h" #include "Error.h" -#include "ModuleFactory.h" #include "Core.h" #include "Debug.h" #include "PluginManager.h" @@ -105,10 +104,9 @@ using std::string; using std::vector; using namespace DFHack; -const size_t MAX_REPORTS_SIZE = 3000; // DF clears old reports to maintain this vector size -const int32_t RECENT_REPORT_TICKS = 500; // used by UNIT_COMBAT_REPORT_ALL_ACTIVE -const int32_t ANNOUNCE_LINE_DURATION = 100; // time to display each line in announcement bar; 2 sec at 50 GFPS -const int16_t ANNOUNCE_DISPLAY_TIME = 2000; // DF uses this value for most announcements; 40 sec at 50 GFPS +static constexpr int32_t RECENT_REPORT_TICKS = 500; // used by UNIT_COMBAT_REPORT_ALL_ACTIVE +static constexpr int32_t ANNOUNCE_LINE_DURATION = 100; // time to display each line in announcement bar; 2 sec at 50 GFPS +static constexpr int16_t ANNOUNCE_DISPLAY_TIME = 2000; // DF uses this value for most announcements; 40 sec at 50 GFPS namespace DFHack { @@ -135,7 +133,7 @@ static df::layer_object_listst *getLayerList(df::viewscreen_layer *layer, int id } */ -static std::string getNameChunk(virtual_identity *id, int start, int end) +static std::string getNameChunk(const virtual_identity *id, int start, int end) { if (!id) return "UNKNOWN"; @@ -152,9 +150,10 @@ static std::string getNameChunk(virtual_identity *id, int start, int end) */ typedef void (*getFocusStringsHandler)(std::string &str, std::vector &strList, df::viewscreen *screen); -static std::map getFocusStringsHandlers; +static std::map getFocusStringsHandlers; #define VIEWSCREEN(name) df::viewscreen_##name##st + #define DEFINE_GET_FOCUS_STRING_HANDLER(screen_type) \ static void getFocusStrings_##screen_type(const std::string &baseFocus, std::vector &focusStrings, VIEWSCREEN(screen_type) *screen);\ DFHACK_STATIC_ADD_TO_MAP(\ @@ -969,7 +968,7 @@ std::vector Gui::getFocusStrings(df::viewscreen* top) } } - if (virtual_identity *id = virtual_identity::get(top)) + if (const virtual_identity *id = virtual_identity::get(top)) { std::string name = getNameChunk(id, 11, 2); @@ -1841,7 +1840,7 @@ DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announce return -1; else if (message.empty()) { - Core::printerr("Empty announcement %u\n", type); // DF would print this to errorlog.txt + Core::printerr("Empty announcement {}\n", ENUM_AS_STR(type)); // DF would print this to errorlog.txt return -1; } @@ -1927,18 +1926,6 @@ DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announce world->status.display_timer = ANNOUNCE_DISPLAY_TIME; } - // Delete excess reports - while (reports.size() > MAX_REPORTS_SIZE) - { // Report destructor - if (reports[0] != NULL) - { - if (reports[0]->flags.bits.announcement) - erase_from_vector(world->status.announcements, &df::report::id, reports[0]->id); - delete reports[0]; - } - reports.erase(reports.begin()); - } - return world->status.reports.size() - 1; } @@ -1956,6 +1943,8 @@ bool Gui::addCombatReport(df::unit *unit, df::unit_report_type slot, df::report auto alert_type = announcement_alert_type::NONE; switch (slot) { + case unit_report_type::NONE: /* should never happen? */ + return false; case unit_report_type::Combat: world->status.flags.bits.combat = true; alert_type = announcement_alert_type::COMBAT; @@ -2029,14 +2018,6 @@ void Gui::showPopupAnnouncement(std::string message, int color, bool bright) auto &popups = world->status.popups; popups.push_back(popup); - // Delete excess popups - while (popups.size() > MAX_REPORTS_SIZE) - { - if (popups[0] != NULL) - delete popups[0]; - popups.erase(popups.begin()); - } - Gui::MTB_clean(&world->status.mega_text); Gui::MTB_parse(&world->status.mega_text, popups[0]->text); Gui::MTB_set_width(&world->status.mega_text); @@ -2067,17 +2048,17 @@ bool Gui::autoDFAnnouncement(df::announcement_infost info, string message) { // Based on reverse-engineering of "make_announcement" FUN_1400574e0 (v50.11 win64 Steam) if (!world->allow_announcements) { - DEBUG(gui).print("Skipped announcement because world->allow_announcements is false:\n%s\n", message.c_str()); + DEBUG(gui).print("Skipped announcement because world->allow_announcements is false:\n{}\n", message); return false; } else if (!is_valid_enum_item(info.type) || info.type == df::announcement_type::NONE) { - WARN(gui).print("Invalid announcement type:\n%s\n", message.c_str()); + WARN(gui).print("Invalid announcement type:\n{}\n", message); return false; } else if (message.empty()) { - Core::printerr("Empty announcement %u\n", info.type); // DF would print this to errorlog.txt + Core::printerr("Empty announcement {}\n", ENUM_AS_STR(info.type)); // DF would print this to errorlog.txt return false; } @@ -2088,7 +2069,7 @@ bool Gui::autoDFAnnouncement(df::announcement_infost info, string message) { if (!a_flags.bits.A_DISPLAY && !a_flags.bits.DO_MEGA) { - DEBUG(gui).print("Skipped announcement not enabled at all for adventure mode:\n%s\n", message.c_str()); + DEBUG(gui).print("Skipped announcement not enabled at all for adventure mode:\n{}\n", message); return false; } @@ -2102,7 +2083,7 @@ bool Gui::autoDFAnnouncement(df::announcement_infost info, string message) { // Adventure mode reuses a dwarf mode digging designation bit to determine current visibility if (!Maps::isValidTilePos(info.pos) || (Maps::getTileDesignation(info.pos)->whole & 0x10) == 0x0) { - DEBUG(gui).print("Adventure mode announcement not detected:\n%s\n", message.c_str()); + DEBUG(gui).print("Adventure mode announcement not detected:\n{}\n", message); return false; } } @@ -2112,7 +2093,7 @@ bool Gui::autoDFAnnouncement(df::announcement_infost info, string message) { if ((info.unit_a || info.unit_d) && (!info.unit_a || Units::isHidden(info.unit_a)) && (!info.unit_d || Units::isHidden(info.unit_d))) { - DEBUG(gui).print("Dwarf mode announcement not detected:\n%s\n", message.c_str()); + DEBUG(gui).print("Dwarf mode announcement not detected:\n{}\n", message); return false; } @@ -2122,7 +2103,7 @@ bool Gui::autoDFAnnouncement(df::announcement_infost info, string message) { if (!info.unit_a && !info.unit_d) { - DEBUG(gui).print("Skipped UNIT_COMBAT_REPORT because it has no units:\n%s\n", message.c_str()); + DEBUG(gui).print("Skipped UNIT_COMBAT_REPORT because it has no units:\n{}\n", message); return false; } } @@ -2130,12 +2111,12 @@ bool Gui::autoDFAnnouncement(df::announcement_infost info, string message) { if (!a_flags.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE) { - DEBUG(gui).print("Skipped announcement not enabled at all for dwarf mode:\n%s\n", message.c_str()); + DEBUG(gui).print("Skipped announcement not enabled at all for dwarf mode:\n{}\n", message); return false; } else if (!recent_report_any(info.unit_a) && !recent_report_any(info.unit_d)) { - DEBUG(gui).print("Skipped UNIT_COMBAT_REPORT_ALL_ACTIVE because there's no active report:\n%s\n", message.c_str()); + DEBUG(gui).print("Skipped UNIT_COMBAT_REPORT_ALL_ACTIVE because there's no active report:\n{}\n", message); return false; } } @@ -2168,7 +2149,7 @@ bool Gui::autoDFAnnouncement(df::announcement_infost info, string message) if (samp_index >= 0) { - DEBUG(gui).print("Playing sound #%d for announcement.\n", samp_index); + DEBUG(gui).print("Playing sound #{} for announcement.\n", samp_index); //play_sound(musicsound_info, samp_index, 255, true); // g_src/music_and_sound_g.h // TODO: implement sounds } } @@ -2195,7 +2176,7 @@ bool Gui::autoDFAnnouncement(df::announcement_infost info, string message) if (a_flags.bits.D_DISPLAY) world->status.display_timer = info.display_timer; - DEBUG(gui).print("Announcement succeeded as repeat:\n%s\n", message.c_str()); + DEBUG(gui).print("Announcement succeeded as repeat:\n{}\n", message); return true; } } @@ -2265,26 +2246,14 @@ bool Gui::autoDFAnnouncement(df::announcement_infost info, string message) world->status.display_timer = info.display_timer; } - // Delete excess reports - while (reports.size() > MAX_REPORTS_SIZE) - { // Report destructor - if (reports[0] != NULL) - { - if (reports[0]->flags.bits.announcement) - erase_from_vector(world->status.announcements, &df::report::id, reports[0]->id); - delete reports[0]; - } - reports.erase(reports.begin()); - } - if (*gamemode == game_mode::DWARF || // Did dwarf announcement or UCR (*gamemode == game_mode::ADVENTURE && a_flags.bits.A_DISPLAY) || // Did adventure announcement (a_flags.bits.DO_MEGA && !adv_unconscious)) // Did popup { - DEBUG(gui).print("Announcement succeeded and displayed:\n%s\n", message.c_str()); + DEBUG(gui).print("Announcement succeeded and displayed:\n{}\n", message); } else - DEBUG(gui).print("Announcement added internally and to gamelog.txt but didn't qualify to be displayed anywhere:\n%s\n", message.c_str()); + DEBUG(gui).print("Announcement added internally and to gamelog.txt but didn't qualify to be displayed anywhere:\n{}\n", message); return true; } @@ -2490,8 +2459,8 @@ void Gui::MTB_parse(df::markup_text_boxst *mtb, string parse_text) if (buff1 == "VAR") // Color from dipscript var { - DEBUG(gui).print("MTB_parse received:\n[C:VAR:%s:%s]\nwhich is for dipscripts and is unimplemented.\nThe dipscript environment itself is: %s\n", - buff2.c_str(), buff3.c_str(), mtb->environment ? "Active" : "NULL"); + DEBUG(gui).print("MTB_parse received:\n[C:VAR:{}:{}]\nwhich is for dipscripts and is unimplemented.\nThe dipscript environment itself is: {}\n", + buff2, buff3, mtb->environment ? "Active" : "NULL"); //MTB_set_color_on_var(mtb, buff2, buff3); } else @@ -2545,8 +2514,8 @@ void Gui::MTB_parse(df::markup_text_boxst *mtb, string parse_text) string buff_var_name = grab_token_string_pos(parse_text, i, ':'); i += buff_var_name.size(); - DEBUG(gui).print("MTB_parse received:\n[VAR:%s:%s:%s]\nwhich is for dipscripts and is unimplemented.\nThe dipscript environment itself is: %s\n", - buff_format.c_str(), buff_var_type.c_str(), buff_var_name.c_str(), mtb->environment ? "Active" : "NULL"); + DEBUG(gui).print("MTB_parse received:\n[VAR:{}:{}:{}]\nwhich is for dipscripts and is unimplemented.\nThe dipscript environment itself is: {}\n", + buff_format, buff_var_type, buff_var_name, mtb->environment ? "Active" : "NULL"); //MTB_append_variable(mtb, str, buff_format, buff_var_type, buff_var_name); } else if (token_buffer == "R" || token_buffer == "B" || token_buffer == "P") @@ -2683,10 +2652,9 @@ void Gui::MTB_set_width(df::markup_text_boxst *mtb, int32_t n_width) df::widget * Gui::getWidget(df::widget_container *container, string name) { CHECK_NULL_POINTER(container); // ensure the compiler catches the change if we ever fix the template parameters - std::map & orig_field = container->children_by_name; - auto children_by_name = reinterpret_cast> *>(&orig_field); - if (children_by_name->contains(name)) - return (*children_by_name)[name].get(); + auto & children_by_name = container->children_by_name; + if (children_by_name.contains(name)) + return (children_by_name)[name].get(); return NULL; } @@ -2718,7 +2686,7 @@ df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed) return ws; } -df::viewscreen *Gui::getViewscreenByIdentity (virtual_identity &id, int n) +df::viewscreen *Gui::getViewscreenByIdentity (const virtual_identity &id, int n) { bool limit = (n > 0); df::viewscreen *screen = Gui::getCurViewscreen(); diff --git a/library/modules/Hotkey.cpp b/library/modules/Hotkey.cpp new file mode 100644 index 0000000000..6433879d85 --- /dev/null +++ b/library/modules/Hotkey.cpp @@ -0,0 +1,404 @@ +#include "modules/Hotkey.h" + +#include +#include + +#include "Core.h" +#include "ColorText.h" +#include "MiscUtils.h" +#include "PluginManager.h" + +#include "modules/DFSDL.h" +#include "modules/Gui.h" + +#include "df/global_objects.h" +#include "df/viewscreen.h" +#include "df/interfacest.h" + + +using namespace DFHack; +using Hotkey::KeySpec; +using Hotkey::KeyBinding; + +enum HotkeySignal : uint8_t { + None = 0, + CmdReady, + Shutdown, +}; + +static bool operator==(const KeySpec& a, const KeySpec& b) { + return a.modifiers == b.modifiers && a.sym == b.sym && + a.focus.size() == b.focus.size() && + std::equal(a.focus.begin(), a.focus.end(), b.focus.begin()); +} + +// Equality operator for key bindings +static bool operator==(const KeyBinding& a, const KeyBinding& b) { + return a.spec == b.spec && + a.command == b.command && + a.cmdline == b.cmdline; +} + +std::string KeySpec::toString(bool include_focus) const { + std::string out; + if (modifiers & DFH_MOD_CTRL) out += "Ctrl-"; + if (modifiers & DFH_MOD_ALT) out += "Alt-"; + if (modifiers & DFH_MOD_SUPER) out += "Super-"; + if (modifiers & DFH_MOD_SHIFT) out += "Shift-"; + + std::string key_name; + if (this->sym < 0) { + key_name = "MOUSE" + std::to_string(-this->sym); + } else { + key_name = DFSDL::DFSDL_GetKeyName(this->sym); + } + out += key_name; + + if (include_focus && !this->focus.empty()) { + out += "@"; + bool first = true; + for (const auto& fc : this->focus) { + if (first) { + first = false; + out += fc; + } else { + out += "|" + fc; + } + } + } + + return out; +} + +std::optional KeySpec::parse(std::string spec, std::string* err) { + KeySpec out; + + // Determine focus string, if present + size_t focus_idx = spec.find('@'); + if (focus_idx != std::string::npos) { + split_string(&out.focus, spec.substr(focus_idx + 1), "|"); + spec.erase(focus_idx); + } + + // Treat remaining keyspec as lowercase for case-insensitivity. + std::transform(spec.begin(), spec.end(), spec.begin(), tolower); + + // Determine modifier flags + auto match_modifier = [&out, &spec](std::string_view prefix, int mod) { + bool found = spec.starts_with(prefix); + if (found) { + out.modifiers |= mod; + spec.erase(0, prefix.size()); + } + return found; + }; + while (match_modifier("shift-", DFH_MOD_SHIFT) + || match_modifier("ctrl-", DFH_MOD_CTRL) + || match_modifier("alt-", DFH_MOD_ALT) + || match_modifier("super-", DFH_MOD_SUPER)) {} + + out.sym = DFSDL::DFSDL_GetKeyFromName(spec.c_str()); + if (out.sym != SDLK_UNKNOWN) + return out; + + // Attempt to parse as a mouse binding + if (spec.starts_with("mouse")) { + spec.erase(0, 5); + // Read button number, ensuring between 4 and 15 inclusive + try { + int mbutton = std::stoi(spec); + if (mbutton >= 4 && mbutton <= 15) { + out.sym = -mbutton; + return out; + } + } catch (...) { + // If integer parsing fails, it isn't valid + } + if (err) + *err = "Invalid mouse button '" + spec + "', only 4-15 are valid"; + return std::nullopt; + } + + if (err) + *err = "Unknown key '" + spec + "'"; + + // Invalid key binding + return std::nullopt; +} + +bool KeySpec::isDisruptive() const { + // SDLK enum uses the actual characters for a key as its value. + // Escaped values included are Return, Escape, Backspace, and Tab + const std::string essential_key_set = "\r\x1B\b\t -=[]\\;',./"; + + // Letters A-Z, 0-9, and other special keys such as return/escape, and other general typing keys + bool is_essential_key = (this->sym >= SDLK_a && this->sym <= SDLK_z) // A-Z + || (this->sym >= SDLK_0 && this->sym <= SDLK_9) // 0-9 + || (this->sym < CHAR_MAX && essential_key_set.find((char)this->sym) != std::string::npos) + || (this->sym >= SDLK_LEFT && this->sym <= SDLK_UP); // Arrow keys + + // Essential keys are safe, so long as they have a modifier that isn't Shift + if (is_essential_key && !(this->modifiers & ~DFH_MOD_SHIFT)) + return true; + + return false; +} + +// Hotkeys actions are executed from an external thread to avoid deadlocks +// that may occur if running commands from the render or simulation threads. +void HotkeyManager::hotkey_thread_fn() { + auto& core = DFHack::Core::getInstance(); + + std::unique_lock l(lock); + while (true) { + cond.wait(l, [this]() { return this->hotkey_sig != HotkeySignal::None; }); + if (hotkey_sig == HotkeySignal::Shutdown) + return; + if (hotkey_sig != HotkeySignal::CmdReady) + continue; + + // Copy and reset important data, then release the lock + this->hotkey_sig = HotkeySignal::None; + std::string cmd = this->queued_command; + this->queued_command.clear(); + l.unlock(); + + // Attempt execution of command + DFHack::color_ostream_proxy out(core.getConsole()); + auto res = core.runCommand(out, cmd); + if (res == DFHack::CR_NOT_IMPLEMENTED) + out.printerr("Invalid hotkey command: '%s'\n", cmd.c_str()); + l.lock(); + } +} + + +bool HotkeyManager::addKeybind(KeySpec spec, std::string_view cmd) { + // No point in a hotkey with no action + if (cmd.empty()) + return false; + + KeyBinding binding; + binding.spec = std::move(spec); + binding.cmdline = cmd; + size_t space_idx = cmd.find(' '); + binding.command = space_idx == std::string::npos ? cmd : cmd.substr(0, space_idx); + + std::lock_guard l(lock); + auto& bindings = this->bindings[binding.spec.sym]; + for (auto& bind : bindings) { + // Don't set a keybind twice, but return true as there isn't an issue + if (bind == binding) + return true; + } + + bindings.emplace_back(binding); + return true; +} + +bool HotkeyManager::addKeybind(std::string keyspec, std::string_view cmd) { + std::optional spec_opt = KeySpec::parse(std::move(keyspec)); + if (!spec_opt.has_value()) + return false; + return this->addKeybind(spec_opt.value(), cmd); +} + +bool HotkeyManager::removeKeybind(const KeySpec& spec, bool match_focus, std::string_view cmdline) { + std::lock_guard l(lock); + if (!bindings.contains(spec.sym)) + return false; + auto& binds = bindings[spec.sym]; + + auto new_end = std::remove_if(binds.begin(), binds.end(), [match_focus, spec, &cmdline](const auto& v) { + return v.spec.sym == spec.sym + && v.spec.modifiers == spec.modifiers + && (!match_focus || v.spec.focus == spec.focus) + && (cmdline.empty() || v.cmdline == cmdline); + }); + if (new_end == binds.end()) + return false; // No bindings removed + + binds.erase(new_end, binds.end()); + return true; +} + +bool HotkeyManager::removeKeybind(std::string keyspec, bool match_focus, std::string_view cmdline) { + std::optional spec_opt = KeySpec::parse(std::move(keyspec)); + if (!spec_opt.has_value()) + return false; + return this->removeKeybind(spec_opt.value(), match_focus, cmdline); +} + +std::vector HotkeyManager::listKeybinds(const KeySpec& spec) { + std::lock_guard l(lock); + if (!bindings.contains(spec.sym)) + return {}; + + std::vector out; + + auto& binds = bindings[spec.sym]; + for (const auto& bind : binds) { + if (bind.spec.modifiers != spec.modifiers) + continue; + + // If no focus is required, it is always active + if (spec.focus.empty() || bind.spec.focus.empty()) { + out.push_back(bind.cmdline); + continue; + } + + // If a focus is required, determine if search spec if the same or more specific + for (const auto& requested : spec.focus) { + for (const auto& to_match : bind.spec.focus) { + if (prefix_matches(to_match, requested)) + out.push_back("@" + to_match + ":" + bind.cmdline); + } + } + } + + return out; +} + +std::vector HotkeyManager::listKeybinds(std::string keyspec) { + std::optional spec_opt = KeySpec::parse(std::move(keyspec)); + if (!spec_opt.has_value()) + return {}; + return this->listKeybinds(spec_opt.value()); +} + +std::vector HotkeyManager::listActiveKeybinds() { + std::lock_guard l(lock); + std::vector out; + + for(const auto& [_, bind_set] : bindings) { + for (const auto& binding : bind_set) { + if (binding.spec.focus.empty()) { + // Binding always active + out.emplace_back(binding); + continue; + } + for (const auto& focus : binding.spec.focus) { + // Determine if focus string allows this binding + if (Gui::matchFocusString(focus)) { + out.emplace_back(binding); + break; + } + } + } + } + + return out; +} + +std::vector HotkeyManager::listAllKeybinds() { + std::lock_guard l(lock); + std::vector out; + + for (const auto& [_, bind_set] : bindings) { + for (const auto& bind : bind_set) { + out.emplace_back(bind); + } + } + return out; +} + +bool HotkeyManager::handleKeybind(int sym, int modifiers) { + // Ensure gamestate is ready + if (!df::global::gview || !df::global::plotinfo) + return false; + + // Get topmost active screen + df::viewscreen *screen = &df::global::gview->view; + while (screen->child) + screen = screen->child; + + // Map keypad return to return + if (sym == SDLK_KP_ENTER) + sym = SDLK_RETURN; + + std::unique_lock l(lock); + + // If reading input for a keybinding screen, save the input and exit early + if (keybind_save_requested) { + KeySpec spec; + spec.sym = sym; + spec.modifiers = modifiers; + requested_keybind = spec.toString(false); + keybind_save_requested = false; + return true; + } + + if (!bindings.contains(sym)) + return false; + auto& binds = bindings[sym]; + + auto& core = Core::getInstance(); + bool mortal_mode = core.getMortalMode(); + + // Iterate in reverse, prioritizing the last added keybinds + for (const auto& bind : binds | std::views::reverse) { + if (bind.spec.modifiers != modifiers) + continue; + + if (!bind.spec.focus.empty()) { + bool matched = false; + for (const auto& focus : bind.spec.focus) { + if (Gui::matchFocusString(focus)) { + matched = true; + break; + } + } + if (!matched) + continue; + } + + if (!core.getPluginManager()->CanInvokeHotkey(bind.command, screen)) + continue; + + if (mortal_mode && core.isArmokTool(bind.command)) + continue; + + queued_command = bind.cmdline; + hotkey_sig = HotkeySignal::CmdReady; + l.unlock(); + cond.notify_all(); + return true; + } + + return false; +} + +void HotkeyManager::setHotkeyCommand(std::string cmd) { + std::unique_lock l(lock); + queued_command = std::move(cmd); + hotkey_sig = HotkeySignal::CmdReady; + l.unlock(); + cond.notify_all(); +} + +void HotkeyManager::requestKeybindingInput(bool cancel) { + std::lock_guard l(lock); + keybind_save_requested = !cancel; + requested_keybind.clear(); +} + +std::string HotkeyManager::getKeybindingInput() { + std::lock_guard l(lock); + return requested_keybind; +} + +HotkeyManager::HotkeyManager() { + this->hotkey_thread = std::thread(&HotkeyManager::hotkey_thread_fn, this); +} + +HotkeyManager::~HotkeyManager() { + // Set shutdown signal and notify thread + { + std::lock_guard l(lock); + this->hotkey_sig = HotkeySignal::Shutdown; + } + cond.notify_all(); + + if (this->hotkey_thread.joinable()) + this->hotkey_thread.join(); +} diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 5b9e781721..6b5b3ee1d2 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -28,7 +28,6 @@ distribution. #include "Internal.h" #include "MemAccess.h" #include "MiscUtils.h" -#include "ModuleFactory.h" #include "Types.h" #include "VersionInfo.h" @@ -155,7 +154,7 @@ string ItemTypeInfo::getToken() { if (custom) rv += ":" + custom->id; else if (subtype != -1 && type != item_type::PLANT_GROWTH) - rv += stl_sprintf(":%d", subtype); + rv += fmt::format(":{}", subtype); return rv; } @@ -859,7 +858,7 @@ static bool detachItem(df::item *item) if (item->flags.bits.on_ground) { if (!removeItemOnGround(item)) - Core::printerr("Item was marked on_ground, but not in block: %d (%d,%d,%d)\n", + Core::printerr("Item was marked on_ground, but not in block: {} ({},{},{})\n", item->id, item->pos.x, item->pos.y, item->pos.z); item->flags.bits.on_ground = false; return true; @@ -1200,6 +1199,7 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, break; case CATAPULTPARTS: case BALLISTAPARTS: + case BOLT_THROWER_PARTS: case TRAPPARTS: value = 30; break; @@ -1751,6 +1751,37 @@ int Items::getValue(df::item *item, df::caravan_state *caravan) { return value; } +// Automatically choose a growth print variant for the specified plant growth subtype+material +int32_t Items::pickGrowthPrint(int16_t subtype, int16_t mat, int32_t matg) +{ + int growth_print = -1; + // Make sure it's made of a valid plant material, then grab its definition + if (mat >= 419 && mat <= 618 && matg >= 0 && (unsigned)matg < world->raws.plants.all.size()) + { + auto plant_def = world->raws.plants.all[matg]; + // Make sure it subtype is also valid + if (subtype >= 0 && (unsigned)subtype < plant_def->growths.size()) + { + auto growth_def = plant_def->growths[subtype]; + // Try and find a growth print matching the current time + // (in practice, only tree leaves use this for autumn color changes) + for (size_t i = 0; i < growth_def->prints.size(); i++) + { + auto print_def = growth_def->prints[i]; + if (print_def->timing_start <= *df::global::cur_year_tick && *df::global::cur_year_tick <= print_def->timing_end) + { + growth_print = i; + break; + } + } + // If we didn't find one, then pick the first one (if it exists) + if (growth_print == -1 && !growth_def->prints.empty()) + growth_print = 0; + } + } + return growth_print; +} + bool Items::createItem(vector &out_items, df::unit *unit, df::item_type item_type, int16_t item_subtype, int16_t mat_type, int32_t mat_index, bool no_floor, int32_t count) { // Based on Quietust's plugins/createitem.cpp @@ -1796,39 +1827,12 @@ bool Items::createItem(vector &out_items, df::unit *unit, df::item_t World::isFortressMode() ? df::world_site::find(World::GetCurrentSiteId()) : NULL, NULL); delete prod; - DEBUG(items).print("produced %zd items\n", out_items.size()); + DEBUG(items).print("produced {} items\n", out_items.size()); for (auto out_item : out_items) { // Plant growths need a valid "growth print", otherwise they behave oddly if (auto growth = virtual_cast(out_item)) - { - int growth_print = -1; - // Make sure it's made of a valid plant material, then grab its definition - if (growth->mat_type >= 419 && growth->mat_type <= 618 && growth->mat_index >= 0 && (unsigned)growth->mat_index < world->raws.plants.all.size()) - { - auto plant_def = world->raws.plants.all[growth->mat_index]; - // Make sure it subtype is also valid - if (growth->subtype >= 0 && (unsigned)growth->subtype < plant_def->growths.size()) - { - auto growth_def = plant_def->growths[growth->subtype]; - // Try and find a growth print matching the current time - // (in practice, only tree leaves use this for autumn color changes) - for (size_t i = 0; i < growth_def->prints.size(); i++) - { - auto print_def = growth_def->prints[i]; - if (print_def->timing_start <= *df::global::cur_year_tick && *df::global::cur_year_tick <= print_def->timing_end) - { - growth_print = i; - break; - } - } - // If we didn't find one, then pick the first one (if it exists) - if (growth_print == -1 && !growth_def->prints.empty()) - growth_print = 0; - } - } - growth->growth_print = growth_print; - } + growth->growth_print = pickGrowthPrint(growth->subtype, growth->mat_type, growth->mat_index); if (!no_floor) out_item->moveToGround(pos.x, pos.y, pos.z); } @@ -1961,17 +1965,10 @@ bool Items::isRequestedTradeGood(df::item *item, df::caravan_state *caravan) { return false; } -/// When called with game_ui = true, this is equivalent to Bay12's itemst::meltable() -/// (i.e., returning true if and only if the item has a "designate for melting" button in game) -bool Items::canMelt(df::item *item, bool game_ui) { - CHECK_NULL_POINTER(item); - MaterialInfo mat(item); - if (mat.getCraftClass() != craft_material_class::Metal) - return false; - - switch(item->getType()) +bool Items::usesStandardMaterial(df::item_type item_type) +{ + switch(item_type) { using namespace df::enums::item_type; - // These are not meltable, even if made from metal case CORPSE: case CORPSEPIECE: case REMAINS: @@ -1983,8 +1980,20 @@ bool Items::canMelt(df::item *item, bool game_ui) { case EGG: return false; default: - break; + return true; } +} + +/// When called with game_ui = true, this is equivalent to Bay12's itemst::meltable() +/// (i.e., returning true if and only if the item has a "designate for melting" button in game) +bool Items::canMelt(df::item *item, bool game_ui) { + CHECK_NULL_POINTER(item); + MaterialInfo mat(item); + if (mat.getCraftClass() != craft_material_class::Metal) + return false; + + if (!usesStandardMaterial(item->getType())) + return false; if (item->flags.bits.artifact) return false; diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 3c70807325..cbcfebcc23 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -49,6 +49,7 @@ distribution. #include "df/job_list_link.h" #include "df/job_postingst.h" #include "df/job_restrictionst.h" +#include "df/manager_order.h" #include "df/plotinfost.h" #include "df/specific_ref.h" #include "df/unit.h" @@ -686,3 +687,27 @@ std::string Job::getName(df::job *job) return desc; } + +std::string Job::getManagerOrderName(df::manager_order *order) +{ + CHECK_NULL_POINTER(order); + + std::string desc; + auto button = df::allocate(); + button->mstring = order->reaction_name; + button->jobtype = order->job_type; + button->itemtype = order->item_type; + button->subtype = order->item_subtype; + button->material = order->mat_type; + button->matgloss = order->mat_index; + button->specflag = order->specflag; + button->job_item_flag = order->material_category; + button->specdata = order->specdata; + button->art_specifier_id1 = order->art_spec.id; + button->art_specifier_id2 = order->art_spec.subid; + + button->text(&desc); + delete button; + + return desc; +} diff --git a/library/modules/Kitchen.cpp b/library/modules/Kitchen.cpp index d6c2b657f5..c9d4c7d5c6 100644 --- a/library/modules/Kitchen.cpp +++ b/library/modules/Kitchen.cpp @@ -14,7 +14,6 @@ using namespace std; #include "Types.h" #include "Error.h" #include "modules/Kitchen.h" -#include "ModuleFactory.h" #include "Core.h" using namespace DFHack; @@ -33,14 +32,14 @@ void Kitchen::debug_print(color_ostream &out) out.print("Kitchen Exclusions\n"); for(std::size_t i = 0; i < size(); ++i) { - out.print("%2zu: IT:%2i IS:%i MT:%3i MI:%2i ET:%i %s\n", + out.print("{:2}: IT:{:2} IS:{:} MT:{:3} MI:{:2} ET:{:} {}\n", i, - plotinfo->kitchen.item_types[i], + ENUM_KEY_STR(item_type,plotinfo->kitchen.item_types[i]), plotinfo->kitchen.item_subtypes[i], plotinfo->kitchen.mat_types[i], plotinfo->kitchen.mat_indices[i], plotinfo->kitchen.exc_types[i].whole, - (plotinfo->kitchen.mat_types[i] >= 419 && plotinfo->kitchen.mat_types[i] <= 618) ? world->raws.plants.all[plotinfo->kitchen.mat_indices[i]]->id.c_str() : "n/a" + (plotinfo->kitchen.mat_types[i] >= 419 && plotinfo->kitchen.mat_types[i] <= 618) ? world->raws.plants.all[plotinfo->kitchen.mat_indices[i]]->id : "n/a" ); } out.print("\n"); diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp index c01f5bf576..84ee4affa0 100644 --- a/library/modules/MapCache.cpp +++ b/library/modules/MapCache.cpp @@ -30,7 +30,6 @@ distribution. #include "Error.h" #include "MemAccess.h" #include "MiscUtils.h" -#include "ModuleFactory.h" #include "VersionInfo.h" #include "modules/Buildings.h" @@ -97,8 +96,6 @@ const BiomeInfo MapCache::biome_stub = { -1, -1, -1, -1, -1, -1, -1, -1 } }; -#define COPY(a,b) memcpy(&a,&b,sizeof(a)) - MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent), designated_tiles{} @@ -124,20 +121,19 @@ void MapExtras::Block::init() if(block) { - COPY(designation, block->designation); - COPY(occupancy, block->occupancy); - - COPY(temp1, block->temperature_1); - COPY(temp2, block->temperature_2); + designation = block->designation; + occupancy = block->occupancy; + temp1 = block->temperature_1; + temp2 = block->temperature_2; valid = true; } else { - memset(designation,0,sizeof(designation)); - memset(occupancy,0,sizeof(occupancy)); - memset(temp1,0,sizeof(temp1)); - memset(temp2,0,sizeof(temp2)); + designation.fill({}); + occupancy.fill({}); + temp1.fill({}); + temp2.fill({}); } } @@ -199,10 +195,10 @@ void MapExtras::Block::init_tiles(bool basemat) MapExtras::Block::TileInfo::TileInfo() { dirty_raw.clear(); - memset(raw_tiles,0,sizeof(raw_tiles)); + raw_tiles.fill({}); ice_info = NULL; con_info = NULL; - memset(base_tiles,0,sizeof(base_tiles)); + base_tiles.fill({}); } MapExtras::Block::TileInfo::~TileInfo() @@ -219,6 +215,15 @@ void MapExtras::Block::TileInfo::init_iceinfo() ice_info = new IceInfo(); } +template +constexpr T arr40d_neg1() { + T tmp{}; + std::remove_reference_t tmp2{}; + tmp2.fill(-1); + tmp.fill(tmp2); + return tmp; +}; + void MapExtras::Block::TileInfo::init_coninfo() { if (con_info) @@ -226,17 +231,17 @@ void MapExtras::Block::TileInfo::init_coninfo() con_info = new ConInfo(); con_info->constructed.clear(); - COPY(con_info->tiles, base_tiles); - memset(con_info->mat_type, -1, sizeof(con_info->mat_type)); - memset(con_info->mat_index, -1, sizeof(con_info->mat_index)); + con_info->tiles = base_tiles; + con_info->mat_type = arr40d_neg1(); + con_info->mat_index = arr40d_neg1(); } MapExtras::Block::BasematInfo::BasematInfo() { vein_dirty.clear(); - memset(mat_type,0,sizeof(mat_type)); - memset(mat_index,-1,sizeof(mat_index)); - memset(veinmat,-1,sizeof(veinmat)); + mat_type.fill({}); + mat_index = arr40d_neg1(); + veinmat = arr40d_neg1(); } bool MapExtras::Block::setFlagAt(df::coord2d p, df::tile_designation::Mask mask, bool set) @@ -482,7 +487,7 @@ void MapExtras::Block::ParseTiles(TileInfo *tiles) tiletypes40d icetiles; BlockInfo::SquashFrozenLiquids(block, icetiles); - COPY(tiles->raw_tiles, block->tiletype); + tiles->raw_tiles = block->tiletype; for (int x = 0; x < 16; x++) { @@ -599,7 +604,7 @@ void MapExtras::Block::WriteTiles(TileInfo *tiles) if (tiles->ice_info && tiles->ice_info->dirty.has_assignments()) { - df::tiletype (*newtiles)[16] = (tiles->con_info ? tiles->con_info->tiles : tiles->base_tiles); + auto newtiles = (tiles->con_info ? tiles->con_info->tiles : tiles->base_tiles); for (int i = block->block_events.size()-1; i >= 0; i--) { @@ -647,8 +652,8 @@ void MapExtras::Block::ParseBasemats(TileInfo *tiles, BasematInfo *bmats) info.prepare(this); - COPY(bmats->veinmat, info.veinmats); - COPY(bmats->veintype, info.veintype); + bmats->veinmat = info.veinmats; + bmats->veintype = info.veintype; for (int x = 0; x < 16; x++) { @@ -780,7 +785,7 @@ bool MapExtras::Block::Write () if(dirty_designations) { - COPY(block->designation, designation); + block->designation = designation; block->flags.bits.designated = true; block->dsgn_check_cooldown = 0; dirty_designations = false; @@ -799,13 +804,13 @@ bool MapExtras::Block::Write () } if(dirty_temperatures) { - COPY(block->temperature_1, temp1); - COPY(block->temperature_2, temp2); + block->temperature_1 = temp1; + block->temperature_2 = temp2; dirty_temperatures = false; } if(dirty_occupancies) { - COPY(block->occupancy, occupancy); + block->occupancy = occupancy; dirty_occupancies = false; } return true; @@ -1035,8 +1040,8 @@ void MapExtras::BlockInfo::SquashVeins(df::map_block *mb, t_blockmaterials & mat { std::vector veins; Maps::SortBlockEvents(mb,&veins); - memset(materials,-1,sizeof(materials)); - memset(veintype, 0, sizeof(t_veintype)); + materials = arr40d_neg1(); + veintype.fill({}); for (uint32_t x = 0;x<16;x++) for (uint32_t y = 0; y< 16;y++) { @@ -1055,7 +1060,7 @@ void MapExtras::BlockInfo::SquashFrozenLiquids(df::map_block *mb, tiletypes40d & { std::vector ices; Maps::SortBlockEvents(mb,NULL,&ices); - memset(frozen,0,sizeof(frozen)); + frozen.fill({}); for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++) { for (size_t i = 0; i < ices.size(); i++) @@ -1090,7 +1095,7 @@ void MapExtras::BlockInfo::SquashGrass(df::map_block *mb, t_blockmaterials &mate { std::vector grasses; Maps::SortBlockEvents(mb, NULL, NULL, NULL, &grasses); - memset(materials,-1,sizeof(materials)); + materials = arr40d_neg1(); for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++) { int amount = 0; diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 7e5707fccb..d915d963a5 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -31,7 +31,6 @@ distribution. #include "Error.h" #include "MemAccess.h" #include "MiscUtils.h" -#include "ModuleFactory.h" #include "VersionInfo.h" #include "modules/Buildings.h" @@ -41,21 +40,31 @@ distribution. #include "df/biome_type.h" #include "df/block_burrow.h" #include "df/block_burrow_link.h" +#include "df/block_column_print_infost.h" #include "df/block_square_event_grassst.h" +#include "df/block_square_event_item_spatterst.h" +#include "df/block_square_event_material_spatterst.h" +#include "df/block_square_event_spoorst.h" #include "df/building.h" #include "df/building_type.h" #include "df/builtin_mats.h" #include "df/burrow.h" +#include "df/entity_plot_invasion_mapst.h" #include "df/feature_init.h" #include "df/feature_map_shellst.h" #include "df/feature_mapst.h" #include "df/flow_info.h" +#include "df/historical_entity.h" +#include "df/invasion_info.h" #include "df/map_block.h" #include "df/map_block_column.h" +#include "df/material.h" #include "df/plant.h" #include "df/plant_root_tile.h" #include "df/plant_tree_info.h" #include "df/plant_tree_tile.h" +#include "df/plotinfost.h" +#include "df/plot_invasion_mapst.h" #include "df/region_map_entry.h" #include "df/world.h" #include "df/world_data.h" @@ -65,9 +74,12 @@ distribution. #include "df/world_underground_region.h" #include "df/z_level_flags.h" + +#include #include #include #include +#include #include #include #include @@ -651,11 +663,256 @@ bool Maps::SortBlockEvents(df::map_block *block, if (priorities) priorities->push_back((df::block_square_event_designation_priorityst *)evt); break; + default: + assert("Unhandled block event type" && false); // FIXME temporary - replace with NONE case after structure are updated + break; } } return true; } +// Based on worldst::add_material_spatter_tile_capped +int32_t Maps::addMaterialSpatter (df::coord pos, int16_t mat, int32_t matg, df::matter_state state, int32_t amount) +{ + // Hardcoded maximum + int32_t cap = 255; + + // Sanity checks + if (amount > cap) + amount = cap; + // DF doesn't handle negative numbers, so disallow them + if (amount < 0) + amount = 0; + + // DF rejects materials of NONE:* + if (mat == -1) + return amount; + + // Extra check: make sure the material correctly exists + MaterialInfo matinfo(mat, matg); + if (!matinfo.isValid()) + return amount; + + df::map_block *block = Maps::getTileBlock(pos); + if (!block) + return amount; + + int16_t bx = pos.x & 0xF, by = pos.y & 0xF; + + // Extra check: specify state == NONE to auto-pick based on tile temperature + // Note that this won't choose POWDER/PASTE/PRESSED + if (state == df::matter_state::None) + { + uint16_t tile = block->temperature_1[bx][by]; + uint16_t melt = matinfo.material->heat.melting_point; + uint16_t boil = matinfo.material->heat.boiling_point; + if (boil != 60001 && tile >= boil) + state = df::matter_state::Gas; + else if (melt != 60001 && tile >= melt) + state = df::matter_state::Liquid; + else + state = df::matter_state::Solid; + } + + if (amount > 0) + { + // scan all SPOOR events and clear the PRESENT flag if the type is HFID_COMBINEDCASTE_BP, ITEMT_ITEMST_ORIENT, or MESS + for (size_t i = 0; i < block->block_events.size(); i++) + { + df::block_square_event *evt = block->block_events[i]; + if (evt->getType() != block_square_event_type::spoor) + continue; + auto spoor = (df::block_square_event_spoorst *)evt; + if (!spoor->info.flags[bx][by].bits.present) + continue; + if (spoor->info.type[bx][by] == df::spoor_type::HFID_COMBINEDCASTE_BP || + spoor->info.type[bx][by] == df::spoor_type::ITEMT_ITEMST_ORIENT || + spoor->info.type[bx][by] == df::spoor_type::MESS) + spoor->info.flags[bx][by].bits.present = false; + } + } + + // Find existing matching material spatter + df::block_square_event_material_spatterst *spatter = nullptr; + // DF: get_material_spatter_event_even_if_empty(...) + for (int i = block->block_events.size() - 1; i >= 0; i--) + { + df::block_square_event *evt = block->block_events[i]; + if (evt->getType() != block_square_event_type::material_spatter) + continue; + auto spt = (df::block_square_event_material_spatterst *)evt; + if (spt->mat_type == mat && spt->mat_index == matg && + spt->mat_state == state) + { + spatter = spt; + break; + } + } + + // If we didn't find one, make a new one + if (!spatter) + { + spatter = df::allocate(); + spatter->mat_type = mat; + spatter->mat_index = matg; + spatter->mat_state = state; + spatter->amount.fill({}); + spatter->min_temperature = spatter->max_temperature = 60001; + + uint16_t melt = matinfo.material->heat.melting_point; + uint16_t boil = matinfo.material->heat.boiling_point; + + switch (state) + { using namespace df::enums::matter_state; + case Solid: + case Powder: + case Paste: + case Pressed: + if (melt != 60001) + boil = melt; + spatter->max_temperature = boil; + break; + case Liquid: + if (melt != 60001 && melt != 0) + spatter->min_temperature = melt - 1; + spatter->max_temperature = boil; + break; + // Can't really have gas spatters, but DF has this check + // presumably, DF could convert this into a flow + case Gas: + if (boil != 60001 && boil != 0) + spatter->min_temperature = boil - 1; + else if (melt != 60001 && melt != 0) + spatter->min_temperature = melt - 1; + break; + case None: + // impossible + break; + } + // DF doesn't check heatdam/colddam/ignite points here + block->block_events.push_back(spatter); + } + + int32_t newamount = spatter->amount[bx][by] + amount; + if (newamount > cap) + { + amount = newamount - cap; + newamount = cap; + } + else + amount = 0; + + spatter->amount[bx][by] = (uint8_t)newamount; + block->flags.bits.may_have_material_spatter = 1; + + return amount; +} + +// Based on worldst::add_item_spatter_tile_capped +int32_t Maps::addItemSpatter (df::coord pos, df::item_type i_type, int16_t i_subtype, int16_t i_subcat1, int32_t i_subcat2, int32_t print_variant, int32_t amount) +{ + // DF passes this as a parameter, but it's always the same + int32_t cap = 10000; + + // Sanity checks + if (amount > cap) + amount = cap; + // DF doesn't handle negative numbers, so disallow them + if (amount < 0) + amount = 0; + + df::map_block *block = Maps::getTileBlock(pos); + if (!block) + return amount; + + int16_t bx = pos.x & 0xF, by = pos.y & 0xF; + + if (amount > 0) + { + // scan all SPOOR events and clear the PRESENT flag if the type is HFID_COMBINEDCASTE_BP, ITEMT_ITEMST_ORIENT, or MESS + for (size_t i = 0; i < block->block_events.size(); i++) + { + df::block_square_event *evt = block->block_events[i]; + if (evt->getType() != block_square_event_type::spoor) + continue; + auto spoor = (df::block_square_event_spoorst *)evt; + if (!spoor->info.flags[bx][by].bits.present) + continue; + if (spoor->info.type[bx][by] == df::spoor_type::HFID_COMBINEDCASTE_BP || + spoor->info.type[bx][by] == df::spoor_type::ITEMT_ITEMST_ORIENT || + spoor->info.type[bx][by] == df::spoor_type::MESS) + spoor->info.flags[bx][by].bits.present = false; + } + } + + // Allow auto-selecting growth print for plant growths + if (i_type == df::item_type::PLANT_GROWTH && print_variant == -1) + print_variant = Items::pickGrowthPrint(i_subtype, i_subcat1, i_subcat2); + + // Find existing matching item spatter + df::block_square_event_item_spatterst *spatter = nullptr; + // DF: get_item_spatter_event_even_if_empty(...) + for (int i = block->block_events.size() - 1; i >= 0; i--) + { + df::block_square_event *evt = block->block_events[i]; + if (evt->getType() != block_square_event_type::item_spatter) + continue; + auto spt = (df::block_square_event_item_spatterst *)evt; + if (spt->item_type == i_type && spt->item_subtype == i_subtype && + spt->mattype == i_subcat1 && spt->matindex == i_subcat2 && + spt->print_variant == print_variant) + { + spatter = spt; + break; + } + } + + // If we didn't find one, make a new one + if (!spatter) + { + spatter = df::allocate(); + spatter->item_type = i_type; + spatter->item_subtype = i_subtype; + spatter->mattype = i_subcat1; + spatter->matindex = i_subcat2; + spatter->print_variant = print_variant; + spatter->amount.fill({}); + spatter->flag.fill({}); + spatter->min_temperature = spatter->max_temperature = 60001; + + if (Items::usesStandardMaterial(i_type)) + { + MaterialInfo info(i_subcat1, i_subcat2); + if (info.isValid()) + { + uint16_t melt = info.material->heat.melting_point; + uint16_t boil = info.material->heat.melting_point; + if (melt != 60001) + spatter->max_temperature = melt; + else + spatter->max_temperature = boil; + // DF doesn't look at the heatdam/colddam/ignite temperatures + } + } + block->block_events.push_back(spatter); + } + + int32_t newamount = spatter->amount[bx][by] + amount; + if (newamount > cap) + { + amount = newamount - cap; + newamount = cap; + } + else + amount = 0; + + spatter->amount[bx][by] = newamount; + spatter->flag[bx][by].bits.season_full_timer = 7; + block->flags.bits.may_have_item_spatter = 1; + + return amount; +} + inline bool RemoveBlockEventInline(int32_t x, int32_t y, int32_t z, df::block_square_event * which) { df::map_block *block = Maps::getBlock(x, y, z); @@ -1274,3 +1531,124 @@ int Maps::removeAreaAquifer(df::coord pos1, df::coord pos2, std::functionmap.z_count_block; + if (quantity <= 0) + return; + + auto world = df::global::world; + int32_t z_count_block = world->map.z_count_block; + df::map_block**** block_index = world->map.block_index; + + cuboid last_air_layer( + 0, 0, world->map.z_count_block - 1, + world->map.x_count_block - 1, world->map.y_count_block - 1, world->map.z_count_block - 1); + + last_air_layer.forCoord([&] (df::coord bpos) { + // Allocate a new block column and copy over data from the old + df::map_block** blockColumn = + new df::map_block * [z_count_block + quantity]; + std::memcpy(blockColumn, block_index[bpos.x][bpos.y], + z_count_block * sizeof(df::map_block*)); + delete[] block_index[bpos.x][bpos.y]; + block_index[bpos.x][bpos.y] = blockColumn; + + df::map_block* last_air_block = blockColumn[bpos.z]; + for (int32_t count = 0; count < quantity; count++) + { + df::map_block* air_block = new df::map_block(); + std::fill(&air_block->tiletype[0][0], + &air_block->tiletype[0][0] + (16 * 16), + df::tiletype::OpenSpace); + + // Set block positions properly (based on prior air layer) + air_block->map_pos = last_air_block->map_pos + df::coord{0, 0, uint16_t(count + 1)}; + air_block->region_pos = last_air_block->region_pos; + + // Copy other potentially important metadata from prior air + // layer + air_block->lighting = last_air_block->lighting; + air_block->temperature_1 = last_air_block->temperature_1; + air_block->temperature_2 = last_air_block->temperature_2; + air_block->region_offset = last_air_block->region_offset; + + // Create tile designations to inform lighting and + // outside markers + df::tile_designation designation{}; + designation.bits.light = true; + designation.bits.outside = true; + std::fill(&air_block->designation[0][0], + &air_block->designation[0][0] + (16 * 16), designation); + + blockColumn[z_count_block + count] = air_block; + world->map.map_blocks.push_back(air_block); + + // deal with map_block_column stuff even though it'd probably be + // fine + df::map_block_column* column = + world->map.column_index[bpos.x][bpos.y]; + if (!column) + { + continue; + } + df::block_column_print_infost* glyphs = new df::block_column_print_infost; + glyphs->x = {0,1,2,3}; + glyphs->y = {0,0,0,0}; + glyphs->tile = {'e','x','p','^'}; + column->unmined_glyphs.push_back(glyphs); + } + return true; + }); + + // Update global z level flags + df::z_level_flags* flags = new df::z_level_flags[z_count_block + quantity]; + memcpy(flags, world->map_extras.z_level_flags, + z_count_block * sizeof(df::z_level_flags)); + for (int32_t count = 0; count < quantity; count++) + { + flags[z_count_block + count].whole = 0; + flags[z_count_block + count].bits.update = 1; + } + world->map.z_count_block += quantity; + world->map.z_count += quantity; + delete[] world->map_extras.z_level_flags; + world->map_extras.z_level_flags = flags; + + auto updateInvasionMap = [](int32_t new_height, df::plot_invasion_mapst & map) -> void + { + if (map.blockz == 0) + return; // Unused invasion map + if (map.blockz >= new_height) + return; // No change required + + cuboid blocks(0, 0, 0, map.blockx - 1, map.blocky - 1, 0); + blocks.forCoord([&] (df::coord bpos) { + // Create new vertical block + df::pim_blockst** new_block = new df::pim_blockst * [new_height](); + std::memcpy(new_block, map.block_index[bpos.x][bpos.y], map.blockz * sizeof(df::pim_blockst*)); + // Fill new block with nullptr (no information) + std::fill_n(&new_block[map.blockz], new_height - map.blockz, nullptr); + delete[] map.block_index[bpos.x][bpos.y]; + map.block_index[bpos.x][bpos.y] = new_block; + return true; + }); + + map.blockz = new_height; + }; + + auto plotinfo = df::global::plotinfo; + + for (auto& invasion : plotinfo->invasions.list) + { + updateInvasionMap(world->map.z_count, invasion->map); + } + for (auto& entity : world->entities.all) + { + for (auto& map : entity->plot_invasion_map | std::views::filter([&](df::entity_plot_invasion_mapst* map) { return map->site_id == plotinfo->site_id; })) + { + updateInvasionMap(world->map.z_count, map->map); + } + } +} diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 18dd73b09a..dba8a78132 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -28,7 +28,6 @@ distribution. #include "VersionInfo.h" #include "MemAccess.h" #include "Error.h" -#include "ModuleFactory.h" #include "Core.h" #include "MiscUtils.h" @@ -68,7 +67,7 @@ using namespace df::enums; using df::global::world; using df::global::plotinfo; -bool MaterialInfo::decode(df::item *item) +bool MaterialInfo::decode(df::item* item) { if (!item) return decode(-1); @@ -76,7 +75,7 @@ bool MaterialInfo::decode(df::item *item) return decode(item->getActualMaterial(), item->getActualMaterialIndex()); } -bool MaterialInfo::decode(const df::material_vec_ref &vr, int idx) +bool MaterialInfo::decode(const df::material_vec_ref& vr, int idx) { if (size_t(idx) >= vr.mat_type.size() || size_t(idx) >= vr.mat_index.size()) return decode(-1); @@ -94,14 +93,15 @@ bool MaterialInfo::decode(int16_t type, int32_t index) inorganic = NULL; plant = NULL; creature = NULL; figure = NULL; - if (type < 0) { + if (type < 0) + { mode = None; return false; } - auto &raws = world->raws; + auto& raws = world->raws; - if (size_t(type) >= sizeof(raws.mat_table.builtin)/sizeof(void*)) + if (size_t(type) >= sizeof(raws.mat_table.builtin) / sizeof(void*)) return false; if (index < 0) @@ -123,7 +123,7 @@ bool MaterialInfo::decode(int16_t type, int32_t index) else if (type < FIGURE_BASE) { mode = Creature; - subtype = type-CREATURE_BASE; + subtype = type - CREATURE_BASE; creature = df::creature_raw::find(index); if (!creature || size_t(subtype) >= creature->material.size()) return false; @@ -132,7 +132,7 @@ bool MaterialInfo::decode(int16_t type, int32_t index) else if (type < PLANT_BASE) { mode = Creature; - subtype = type-FIGURE_BASE; + subtype = type - FIGURE_BASE; figure = df::historical_figure::find(index); if (!figure) return false; @@ -144,7 +144,7 @@ bool MaterialInfo::decode(int16_t type, int32_t index) else if (type < END_BASE) { mode = Plant; - subtype = type-PLANT_BASE; + subtype = type - PLANT_BASE; plant = df::plant_raw::find(index); if (!plant || size_t(subtype) >= plant->material.size()) return false; @@ -158,24 +158,24 @@ bool MaterialInfo::decode(int16_t type, int32_t index) return (material != NULL); } -bool MaterialInfo::find(const std::string &token) +bool MaterialInfo::find(const std::string& token) { std::vector items; split_string(&items, token, ":"); return find(items); } -bool MaterialInfo::find(const std::vector &items) +bool MaterialInfo::find(const std::vector& items) { if (items.empty()) return false; if (items[0] == "INORGANIC" && items.size() > 1) - return findInorganic(vector_get(items,1)); + return findInorganic(vector_get(items, 1)); if (items[0] == "CREATURE_MAT" || items[0] == "CREATURE") - return findCreature(vector_get(items,1), vector_get(items,2)); + return findCreature(vector_get(items, 1), vector_get(items, 2)); if (items[0] == "PLANT_MAT" || items[0] == "PLANT") - return findPlant(vector_get(items,1), vector_get(items,2)); + return findPlant(vector_get(items, 1), vector_get(items, 2)); if (items.size() == 1) { @@ -188,7 +188,8 @@ bool MaterialInfo::find(const std::vector &items) } else if (items.size() == 2) { - if (items[0] == "COAL" && findBuiltin(items[0])) { + if (items[0] == "COAL" && findBuiltin(items[0])) + { if (items[1] == "COKE") this->index = 0; else if (items[1] == "CHARCOAL") @@ -206,17 +207,18 @@ bool MaterialInfo::find(const std::vector &items) return false; } -bool MaterialInfo::findBuiltin(const std::string &token) +bool MaterialInfo::findBuiltin(const std::string& token) { if (token.empty()) return decode(-1); - if (token == "NONE") { + if (token == "NONE") + { decode(-1); return true; } - auto &raws = world->raws; + auto& raws = world->raws; for (int i = 0; i < NUM_BUILTIN; i++) { auto obj = raws.mat_table.builtin[i]; @@ -226,34 +228,35 @@ bool MaterialInfo::findBuiltin(const std::string &token) return decode(-1); } -bool MaterialInfo::findInorganic(const std::string &token) +bool MaterialInfo::findInorganic(const std::string& token) { if (token.empty()) return decode(-1); - if (token == "NONE") { + if (token == "NONE") + { decode(0, -1); return true; } - auto &raws = world->raws; + auto& raws = world->raws; for (size_t i = 0; i < raws.inorganics.all.size(); i++) { - df::inorganic_raw *p = raws.inorganics.all[i]; + df::inorganic_raw* p = raws.inorganics.all[i]; if (p->id == token) return decode(0, i); } return decode(-1); } -bool MaterialInfo::findPlant(const std::string &token, const std::string &subtoken) +bool MaterialInfo::findPlant(const std::string& token, const std::string& subtoken) { if (token.empty()) return decode(-1); - auto &raws = world->raws; + auto& raws = world->raws; for (size_t i = 0; i < raws.plants.all.size(); i++) { - df::plant_raw *p = raws.plants.all[i]; + df::plant_raw* p = raws.plants.all[i]; if (p->id != token) continue; @@ -263,39 +266,39 @@ bool MaterialInfo::findPlant(const std::string &token, const std::string &subtok for (size_t j = 0; j < p->material.size(); j++) if (p->material[j]->id == subtoken) - return decode(PLANT_BASE+j, i); + return decode(PLANT_BASE + j, i); break; } return decode(-1); } -bool MaterialInfo::findCreature(const std::string &token, const std::string &subtoken) +bool MaterialInfo::findCreature(const std::string& token, const std::string& subtoken) { if (token.empty() || subtoken.empty()) return decode(-1); - auto &raws = world->raws; + auto& raws = world->raws; for (size_t i = 0; i < raws.creatures.all.size(); i++) { - df::creature_raw *p = raws.creatures.all[i]; + df::creature_raw* p = raws.creatures.all[i]; if (p->creature_id != token) continue; for (size_t j = 0; j < p->material.size(); j++) if (p->material[j]->id == subtoken) - return decode(CREATURE_BASE+j, i); + return decode(CREATURE_BASE + j, i); break; } return decode(-1); } -bool MaterialInfo::findProduct(df::material *material, const std::string &name) +bool MaterialInfo::findProduct(df::material* material, const std::string& name) { if (!material || name.empty()) return decode(-1); - auto &pids = material->reaction_product.id; + auto& pids = material->reaction_product.id; for (size_t i = 0; i < pids.size(); i++) if ((*pids[i]) == name) return decode(material->reaction_product.material, i); @@ -309,11 +312,13 @@ std::string MaterialInfo::getToken() const return "NONE"; if (!material) - return stl_sprintf("INVALID:%d:%d", type, index); + return fmt::format("INVALID:{}:{}", type, index); - switch (mode) { + switch (mode) + { case Builtin: - if (material->id == "COAL") { + if (material->id == "COAL") + { if (index == 0) return "COAL:COKE"; else if (index == 1) @@ -327,7 +332,7 @@ std::string MaterialInfo::getToken() const case Plant: return "PLANT:" + plant->id + ":" + material->id; default: - return stl_sprintf("INVALID_MODE:%d:%d", type, index); + return fmt::format("INVALID_MODE:{}:{}", type, index); } } @@ -337,7 +342,7 @@ std::string MaterialInfo::toString(uint16_t temp, bool named) const return "any"; if (!material) - return stl_sprintf("INVALID:%d:%d", type, index); + return fmt::format("INVALID:{}:{}", type, index); df::matter_state state = matter_state::Solid; if (temp >= material->heat.melting_point) @@ -350,7 +355,7 @@ std::string MaterialInfo::toString(uint16_t temp, bool named) const name = material->prefix + " " + name; if (named && figure) - name += stl_sprintf(" of HF %d", index); + name += fmt::format(" of HF {}", index); return name; } @@ -382,10 +387,10 @@ bool MaterialInfo::isAnyCloth() const material->flags.is_set(THREAD_PLANT) || material->flags.is_set(SILK) || material->flags.is_set(YARN) - ); + ); } -bool MaterialInfo::matches(const df::job_material_category &cat) const +bool MaterialInfo::matches(const df::job_material_category& cat) const { if (!material) return false; @@ -414,7 +419,7 @@ bool MaterialInfo::matches(const df::job_material_category &cat) const return false; } -bool MaterialInfo::matches(const df::dfhack_material_category &cat) const +bool MaterialInfo::matches(const df::dfhack_material_category& cat) const { if (!material) return false; @@ -443,7 +448,7 @@ bool MaterialInfo::matches(const df::dfhack_material_category &cat) const #undef TEST -bool MaterialInfo::matches(const df::job_item &jitem, df::item_type itype) const +bool MaterialInfo::matches(const df::job_item& jitem, df::item_type itype) const { if (!isValid()) return false; @@ -460,11 +465,11 @@ bool MaterialInfo::matches(const df::job_item &jitem, df::item_type itype) const mask2.whole &= ~xmask2.whole; return bits_match(jitem.flags1.whole, ok1.whole, mask1.whole) && - bits_match(jitem.flags2.whole, ok2.whole, mask2.whole) && - bits_match(jitem.flags3.whole, ok3.whole, mask3.whole); + bits_match(jitem.flags2.whole, ok2.whole, mask2.whole) && + bits_match(jitem.flags3.whole, ok3.whole, mask3.whole); } -void MaterialInfo::getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &mask) const +void MaterialInfo::getMatchBits(df::job_item_flags1& ok, df::job_item_flags1& mask) const { ok.whole = mask.whole = 0; if (!isValid()) return; @@ -486,10 +491,10 @@ void MaterialInfo::getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &ma TEST(processable_to_vial, structural && FLAG(plant, plant_raw_flags::EXTRACT_VIAL)); TEST(processable_to_barrel, structural && FLAG(plant, plant_raw_flags::EXTRACT_BARREL)); TEST(solid, !(MAT_FLAG(ALCOHOL_PLANT) || - MAT_FLAG(ALCOHOL_CREATURE) || - MAT_FLAG(LIQUID_MISC_PLANT) || - MAT_FLAG(LIQUID_MISC_CREATURE) || - MAT_FLAG(LIQUID_MISC_OTHER))); + MAT_FLAG(ALCOHOL_CREATURE) || + MAT_FLAG(LIQUID_MISC_PLANT) || + MAT_FLAG(LIQUID_MISC_CREATURE) || + MAT_FLAG(LIQUID_MISC_OTHER))); TEST(tameable_vermin, false); TEST(sharpenable, MAT_FLAG(IS_STONE)); TEST(milk, linear_index(material->reaction_product.id, std::string("CHEESE_MAT")) >= 0); @@ -497,7 +502,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &ma //04000000 - "milkable" - vtable[107],1,1 } -void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &mask) const +void MaterialInfo::getMatchBits(df::job_item_flags2& ok, df::job_item_flags2& mask) const { ok.whole = mask.whole = 0; if (!isValid()) return; @@ -511,15 +516,15 @@ void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &ma TEST(glass_making, MAT_FLAG(CRYSTAL_GLASSABLE)); TEST(fire_safe, material->heat.melting_point > 11000 - && material->heat.boiling_point > 11000 - && material->heat.ignite_point > 11000 - && material->heat.heatdam_point > 11000 - && (material->heat.colddam_point == 60001 || material->heat.colddam_point < 11000)); + && material->heat.boiling_point > 11000 + && material->heat.ignite_point > 11000 + && material->heat.heatdam_point > 11000 + && (material->heat.colddam_point == 60001 || material->heat.colddam_point < 11000)); TEST(magma_safe, material->heat.melting_point > 12000 - && material->heat.boiling_point > 12000 - && material->heat.ignite_point > 12000 - && material->heat.heatdam_point > 12000 - && (material->heat.colddam_point == 60001 || material->heat.colddam_point < 12000)); + && material->heat.boiling_point > 12000 + && material->heat.ignite_point > 12000 + && material->heat.heatdam_point > 12000 + && (material->heat.colddam_point == 60001 || material->heat.colddam_point < 12000)); TEST(deep_material, FLAG(inorganic, inorganic_flags::SPECIAL)); TEST(non_economic, !inorganic || !(plotinfo && vector_get(plotinfo->economic_stone, index))); @@ -537,7 +542,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &ma TEST(yarn, MAT_FLAG(YARN)); } -void MaterialInfo::getMatchBits(df::job_item_flags3 &ok, df::job_item_flags3 &mask) const +void MaterialInfo::getMatchBits(df::job_item_flags3& ok, df::job_item_flags3& mask) const { ok.whole = mask.whole = 0; if (!isValid()) return; @@ -549,7 +554,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags3 &ok, df::job_item_flags3 &ma #undef FLAG #undef TEST -bool DFHack::parseJobMaterialCategory(df::job_material_category *cat, const std::string &token) +bool DFHack::parseJobMaterialCategory(df::job_material_category* cat, const std::string& token) { cat->whole = 0; @@ -565,7 +570,7 @@ bool DFHack::parseJobMaterialCategory(df::job_material_category *cat, const std: return true; } -bool DFHack::parseJobMaterialCategory(df::dfhack_material_category *cat, const std::string &token) +bool DFHack::parseJobMaterialCategory(df::dfhack_material_category* cat, const std::string& token) { cat->whole = 0; @@ -600,32 +605,14 @@ bool DFHack::isStoneInorganic(int material) return true; } -std::unique_ptr DFHack::createMaterials() -{ - return std::make_unique(); -} - -Materials::Materials() -{ -} - -Materials::~Materials() -{ -} - -bool Materials::Finish() -{ - return true; -} - t_matgloss::t_matgloss() { - fore = 0; - back = 0; - bright = 0; + fore = 0; + back = 0; + bright = 0; - value = 0; - wall_tile = 0; + value = 0; + wall_tile = 0; boulder_tile = 0; } @@ -643,240 +630,7 @@ bool t_matglossInorganic::isGem() return is_gem; } -bool Materials::CopyInorganicMaterials (std::vector & inorganic) -{ - size_t size = world->raws.inorganics.all.size(); - inorganic.clear(); - inorganic.reserve (size); - for (size_t i = 0; i < size;i++) - { - df::inorganic_raw *orig = world->raws.inorganics.all[i]; - t_matglossInorganic mat; - mat.id = orig->id; - mat.name = orig->material.stone_name; - - mat.ore_types = orig->metal_ore.mat_index; - mat.ore_chances = orig->metal_ore.probability; - mat.strand_types = orig->thread_metal.mat_index; - mat.strand_chances = orig->thread_metal.probability; - mat.value = orig->material.material_value; - mat.wall_tile = orig->material.tile; - mat.boulder_tile = orig->material.item_symbol; - mat.fore = orig->material.basic_color[0]; - mat.bright = orig->material.basic_color[1]; - mat.is_gem = orig->material.flags.is_set(material_flags::IS_GEM); - inorganic.push_back(mat); - } - return true; -} - -bool Materials::CopyOrganicMaterials (std::vector & organic) -{ - size_t size = world->raws.plants.all.size(); - organic.clear(); - organic.reserve (size); - for (size_t i = 0; i < size;i++) - { - t_matgloss mat; - mat.id = world->raws.plants.all[i]->id; - organic.push_back(mat); - } - return true; -} - -bool Materials::CopyWoodMaterials (std::vector & tree) -{ - size_t size = world->raws.plants.trees.size(); - tree.clear(); - tree.reserve (size); - for (size_t i = 0; i < size;i++) - { - t_matgloss mat; - mat.id = world->raws.plants.trees[i]->id; - tree.push_back(mat); - } - return true; -} - -bool Materials::CopyPlantMaterials (std::vector & plant) -{ - size_t size = world->raws.plants.bushes.size(); - plant.clear(); - plant.reserve (size); - for (size_t i = 0; i < size;i++) - { - t_matgloss mat; - mat.id = world->raws.plants.bushes[i]->id; - plant.push_back(mat); - } - return true; -} - -bool Materials::ReadCreatureTypes (void) -{ - size_t size = world->raws.creatures.all.size(); - race.clear(); - race.reserve (size); - for (size_t i = 0; i < size;i++) - { - t_matgloss mat; - mat.id = world->raws.creatures.all[i]->creature_id; - race.push_back(mat); - } - return true; -} - -bool Materials::ReadOthers(void) -{ - other.clear(); - FOR_ENUM_ITEMS(builtin_mats, i) - { - t_matglossOther mat; - mat.id = world->raws.mat_table.builtin[i]->id; - other.push_back(mat); - } - return true; -} - -bool Materials::ReadDescriptorColors (void) -{ - size_t size = world->raws.descriptors.colors.size(); - - color.clear(); - if(size == 0) - return false; - color.reserve(size); - for (size_t i = 0; i < size;i++) - { - df::descriptor_color *c = world->raws.descriptors.colors[i]; - t_descriptor_color col; - col.id = c->id; - col.name = c->name; - col.red = c->red; - col.green = c->green; - col.blue = c->blue; - color.push_back(col); - } - - size = world->raws.descriptors.patterns.size(); - alldesc.clear(); - alldesc.reserve(size); - for (size_t i = 0; i < size;i++) - { - t_matgloss mat; - mat.id = world->raws.descriptors.patterns[i]->id; - alldesc.push_back(mat); - } - return true; -} - -bool Materials::ReadCreatureTypesEx (void) -{ - size_t size = world->raws.creatures.all.size(); - raceEx.clear(); - raceEx.reserve (size); - for (size_t i = 0; i < size; i++) - { - df::creature_raw *cr = world->raws.creatures.all[i]; - t_creaturetype mat; - mat.id = cr->creature_id; - mat.tile_character = cr->creature_tile; - mat.tilecolor.fore = cr->color[0]; - mat.tilecolor.back = cr->color[1]; - mat.tilecolor.bright = cr->color[2]; - - size_t sizecas = cr->caste.size(); - for (size_t j = 0; j < sizecas;j++) - { - df::caste_raw *ca = cr->caste[j]; - /* caste name */ - t_creaturecaste caste; - caste.id = ca->caste_id; - caste.singular = ca->caste_name[0]; - caste.plural = ca->caste_name[1]; - caste.adjective = ca->caste_name[2]; - - // color mod reading - // Caste + offset > color mod vector - auto & colorings = ca->color_modifiers; - size_t sizecolormod = colorings.size(); - caste.ColorModifier.resize(sizecolormod); - for(size_t k = 0; k < sizecolormod;k++) - { - // color mod [0] -> color list - auto & indexes = colorings[k]->pattern_index; - size_t sizecolorlist = indexes.size(); - caste.ColorModifier[k].colorlist.resize(sizecolorlist); - for(size_t l = 0; l < sizecolorlist; l++) - caste.ColorModifier[k].colorlist[l] = indexes[l]; - // color mod [color_modifier_part_offset] = string part - caste.ColorModifier[k].part = colorings[k]->part; - caste.ColorModifier[k].startdate = colorings[k]->start_date; - caste.ColorModifier[k].enddate = colorings[k]->end_date; - } - - // body parts - caste.bodypart.clear(); - size_t sizebp = ca->body_info.body_parts.size(); - for (size_t k = 0; k < sizebp; k++) - { - df::body_part_raw *bp = ca->body_info.body_parts[k]; - t_bodypart part; - part.id = bp->token; - part.category = bp->category; - caste.bodypart.push_back(part); - } - using namespace df::enums::mental_attribute_type; - using namespace df::enums::physical_attribute_type; - for (int32_t k = 0; k < 7; k++) - { - auto & physical = ca->attributes.phys_att_range; - caste.strength[k] = physical[STRENGTH][k]; - caste.agility[k] = physical[AGILITY][k]; - caste.toughness[k] = physical[TOUGHNESS][k]; - caste.endurance[k] = physical[ENDURANCE][k]; - caste.recuperation[k] = physical[RECUPERATION][k]; - caste.disease_resistance[k] = physical[DISEASE_RESISTANCE][k]; - - auto & mental = ca->attributes.ment_att_range; - caste.analytical_ability[k] = mental[ANALYTICAL_ABILITY][k]; - caste.focus[k] = mental[FOCUS][k]; - caste.willpower[k] = mental[WILLPOWER][k]; - caste.creativity[k] = mental[CREATIVITY][k]; - caste.intuition[k] = mental[INTUITION][k]; - caste.patience[k] = mental[PATIENCE][k]; - caste.memory[k] = mental[MEMORY][k]; - caste.linguistic_ability[k] = mental[LINGUISTIC_ABILITY][k]; - caste.spatial_sense[k] = mental[SPATIAL_SENSE][k]; - caste.musicality[k] = mental[MUSICALITY][k]; - caste.kinesthetic_sense[k] = mental[KINESTHETIC_SENSE][k]; - caste.empathy[k] = mental[EMPATHY][k]; - caste.social_awareness[k] = mental[SOCIAL_AWARENESS][k]; - } - mat.castes.push_back(caste); - } - for (size_t j = 0; j < world->raws.creatures.all[i]->material.size(); j++) - { - t_creatureextract extract; - extract.id = world->raws.creatures.all[i]->material[j]->id; - mat.extract.push_back(extract); - } - raceEx.push_back(mat); - } - return true; -} - -bool Materials::ReadAllMaterials(void) -{ - bool ok = true; - ok &= this->ReadCreatureTypes(); - ok &= this->ReadCreatureTypesEx(); - ok &= this->ReadDescriptorColors(); - ok &= this->ReadOthers(); - return ok; -} - -std::string Materials::getDescription(const t_material & mat) +std::string Materials::getDescription(const t_material& mat) { MaterialInfo mi(mat.mat_type, mat.mat_index); if (mi.creature) @@ -889,7 +643,7 @@ std::string Materials::getDescription(const t_material & mat) // type of material only so we know which vector to retrieve // This is completely worthless now -std::string Materials::getType(const t_material & mat) +std::string Materials::getType(const t_material& mat) { MaterialInfo mi(mat.mat_type, mat.mat_index); switch (mi.mode) diff --git a/library/modules/Persistence.cpp b/library/modules/Persistence.cpp index e6a0d50126..b9c3df577b 100644 --- a/library/modules/Persistence.cpp +++ b/library/modules/Persistence.cpp @@ -303,7 +303,7 @@ void Persistence::Internal::load(color_ostream& out) { std::filesystem::path save_path = getSavePath(world_name); std::vector files; if (0 != Filesystem::listdir(save_path, files)) { - DEBUG(persistence,out).print("not loading state; save directory doesn't exist: '%s'\n", save_path.c_str()); + DEBUG(persistence,out).print("not loading state; save directory doesn't exist: '{}'\n", save_path); return; } @@ -316,7 +316,7 @@ void Persistence::Internal::load(color_ostream& out) { found = true; std::filesystem::path path = save_path / fname; if (!load_file(path, entity_id)) - out.printerr("Cannot load data from: '%s'\n", path.c_str()); + out.printerr("Cannot load data from: '{}'\n", path); } if (found) diff --git a/library/modules/Random.cpp b/library/modules/Random.cpp index f0d2054c9e..d6d682d921 100644 --- a/library/modules/Random.cpp +++ b/library/modules/Random.cpp @@ -35,7 +35,6 @@ using namespace std; #include "VersionInfo.h" #include "MemAccess.h" #include "Types.h" -#include "ModuleFactory.h" #include "Core.h" #include "Error.h" #include "VTableInterpose.h" diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 1a98837739..338a66925e 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -28,7 +28,6 @@ distribution. #include "VersionInfo.h" #include "Types.h" #include "Error.h" -#include "ModuleFactory.h" #include "Core.h" #include "PluginManager.h" #include "LuaTools.h" @@ -136,13 +135,8 @@ static bool doSetTile_map(const Pen &pen, int x, int y, int32_t * df::graphic_vi return true; } -static bool doSetTile_default(const Pen &pen, int x, int y, bool map, int32_t * df::graphic_viewportst::*texpos_field) +static bool doSetTile_char(const Pen &pen, int x, int y, bool use_graphics) { - bool use_graphics = Screen::inGraphicsMode(); - - if (map && use_graphics) - return doSetTile_map(pen, x, y, texpos_field); - if (x < 0 || x >= gps->dimx || y < 0 || y >= gps->dimy) return false; @@ -227,6 +221,16 @@ static bool doSetTile_default(const Pen &pen, int x, int y, bool map, int32_t * return true; } +static bool doSetTile_default(const Pen &pen, int x, int y, bool map, int32_t * df::graphic_viewportst::*texpos_field) +{ + bool use_graphics = Screen::inGraphicsMode(); + + if (map && use_graphics) + return doSetTile_map(pen, x, y, texpos_field); + + return doSetTile_char(pen, x, y, use_graphics); +} + GUI_HOOK_DEFINE(Screen::Hooks::set_tile, doSetTile_default); static bool doSetTile(const Pen &pen, int x, int y, bool map, int32_t * df::graphic_viewportst::*texpos_field = NULL) { @@ -287,12 +291,7 @@ static uint8_t to_16_bit_color(uint8_t *rgb) { return 0; } -static Pen doGetTile_default(int x, int y, bool map, int32_t * df::graphic_viewportst::*texpos_field = NULL) { - bool use_graphics = Screen::inGraphicsMode(); - - if (map && use_graphics) - return doGetTile_map(x, y, texpos_field); - +static Pen doGetTile_char(int x, int y, bool use_graphics) { if (x < 0 || x >= gps->dimx || y < 0 || y >= gps->dimy) return Pen(0, 0, 0, -1); @@ -352,6 +351,14 @@ static Pen doGetTile_default(int x, int y, bool map, int32_t * df::graphic_viewp return ret; } +static Pen doGetTile_default(int x, int y, bool map, int32_t * df::graphic_viewportst::*texpos_field = NULL) { + bool use_graphics = Screen::inGraphicsMode(); + + if (map && use_graphics) + return doGetTile_map(x, y, texpos_field); + return doGetTile_char(x, y, use_graphics); +} + GUI_HOOK_DEFINE(Screen::Hooks::get_tile, doGetTile_default); static Pen doGetTile(int x, int y, bool map, int32_t * df::graphic_viewportst::*texpos_field = NULL) { @@ -365,6 +372,64 @@ Pen Screen::readTile(int x, int y, bool map, int32_t * df::graphic_viewportst::* return doGetTile(x, y, map, texpos_field); } +bool Screen::paintMapPortTile(const Pen &pen, int x, int y, int32_t * df::graphic_map_portst::*texpos_field) +{ + if (!gps || !pen.valid()) return false; + + bool use_graphics = Screen::inGraphicsMode(); + if (!use_graphics) + return doSetTile_char(pen, x, y, use_graphics); + + if (!texpos_field) + texpos_field = &df::graphic_map_portst::screentexpos_interface; + + auto &vp = gps->main_map_port; + if (x < 0 || x >= vp->dim_x || y < 0 || y >= vp->dim_y) + return false; + + size_t max_index = vp->dim_y * vp->dim_x - 1; + size_t index = (y * vp->dim_x) + x; + + if (index > max_index) + return false; + + long texpos = pen.tile; + if (!texpos && pen.ch) + texpos = init->font.large_font_texpos[(uint8_t)pen.ch]; + (vp->*texpos_field)[index] = texpos; + return true; +} + +Pen Screen::readMapPortTile(int x, int y, int32_t * df::graphic_map_portst::*texpos_field) +{ + CHECK_NULL_POINTER(texpos_field) + + if (!gps) return Pen(0,0,0,-1); + + bool use_graphics = Screen::inGraphicsMode(); + + if (!use_graphics) + return doGetTile_char(x, y, use_graphics); + + auto &vp = gps->main_map_port; + + if (x < 0 || x >= vp->dim_x || y < 0 || y >= vp->dim_y) + return Pen(0, 0, 0, -1); + + size_t max_index = vp->dim_x * vp->dim_y - 1; + size_t index = (y * vp->dim_x) + x; + + if (index < 0 || index > max_index) + return Pen(0, 0, 0, -1); + + auto tile = (vp->*texpos_field)[index]; + + char ch = 0; + uint8_t fg = 0; + uint8_t bg = 0; + return Pen(ch, fg, bg, tile, false); +} + bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text, bool map) { auto dim = getWindowSize(); @@ -621,7 +686,7 @@ std::set Screen::normalize_text_keys(const std::setlast_text_input[0]) { char c = df::global::enabler->last_text_input[0]; df::interface_key key = charToKey(c); - DEBUG(screen).print("adding character %c as interface key %ld\n", c, key); + DEBUG(screen).print("adding character {} as interface key {}\n", c, ENUM_AS_STR(key)); combined_keys.emplace(key); } return combined_keys; @@ -780,7 +845,7 @@ void dfhack_viewscreen::logic() bool is_df_screen = !is_instance(p); auto *next_p = p->parent; if (is_df_screen && Screen::isDismissed(p)) { - DEBUG(screen).print("raising dismissed DF viewscreen %p\n", p); + DEBUG(screen).print("raising dismissed DF viewscreen {}\n", static_cast(p)); Screen::raise(p); } if (is_df_screen) diff --git a/library/modules/Textures.cpp b/library/modules/Textures.cpp index 8b485a3a62..4a1e29aeab 100644 --- a/library/modules/Textures.cpp +++ b/library/modules/Textures.cpp @@ -54,7 +54,7 @@ static ReservedRange reserved_range{}; static std::unordered_map g_handle_to_texpos; static std::unordered_map g_handle_to_reserved_texpos; static std::unordered_map g_handle_to_surface; -static std::unordered_map> g_tileset_to_handles; +static std::unordered_map> g_tileset_to_handles; static std::vector g_delayed_regs; static std::mutex g_adding_mutex; static std::atomic loading_state = false; @@ -195,23 +195,23 @@ TexposHandle Textures::loadTexture(SDL_Surface* surface, bool reserved) { return handle; } -std::vector Textures::loadTileset(const std::string& file, int tile_px_w, +std::vector Textures::loadTileset(const std::filesystem::path file, int tile_px_w, int tile_px_h, bool reserved) { if (g_tileset_to_handles.contains(file)) return g_tileset_to_handles[file]; if (!enabler) return std::vector{}; - SDL_Surface* surface = DFIMG_Load(file.c_str()); + SDL_Surface* surface = DFIMG_Load(file.string().c_str()); if (!surface) { - ERR(textures).printerr("unable to load textures from '%s'\n", file.c_str()); + ERR(textures).printerr("unable to load textures from '{}'\n", file); return std::vector{}; } surface = canonicalize_format(surface); 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 {} textures from '{}'\n", handles.size(), file); g_tileset_to_handles[file] = handles; return handles; @@ -308,7 +308,7 @@ static void reset_surface() { } static void register_delayed_handles() { - DEBUG(textures).print("register delayed handles, size %zd\n", g_delayed_regs.size()); + DEBUG(textures).print("register delayed handles, size {}\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); @@ -322,8 +322,9 @@ struct tracking_stage_new_region : df::viewscreen_new_regionst { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { 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 {} -> {}\n", + this->m_raw_load_stage, + static_cast(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) @@ -346,7 +347,9 @@ struct tracking_stage_adopt_region : df::viewscreen_adopt_regionst { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { 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 {} -> {}\n", + this->m_cur_step, + static_cast(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) @@ -369,7 +372,9 @@ struct tracking_stage_load_region : df::viewscreen_loadgamest { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { 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 {} -> {}\n", + this->m_cur_step, + static_cast(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) @@ -392,7 +397,9 @@ struct tracking_stage_new_arena : df::viewscreen_new_arenast { DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { 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 {} -> {}\n", + this->m_cur_step, + static_cast(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) @@ -446,7 +453,7 @@ void Textures::init(color_ostream& out) { reserve_static_range(); install_reset_point(); DEBUG(textures, out) - .print("dynamic texture loading ready, reserved range %d-%d\n", reserved_range.start, + .print("dynamic texture loading ready, reserved range {}-{}\n", reserved_range.start, reserved_range.end); } diff --git a/library/modules/Translation.cpp b/library/modules/Translation.cpp index 38ae2c51ba..097de85cc9 100644 --- a/library/modules/Translation.cpp +++ b/library/modules/Translation.cpp @@ -27,7 +27,6 @@ distribution. #include "VersionInfo.h" #include "MemAccess.h" #include "Types.h" -#include "ModuleFactory.h" #include "Core.h" #include "Error.h" #include "DataDefs.h" diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index d8bfebdc97..00821c11c0 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -27,7 +27,6 @@ distribution. #include "Internal.h" #include "MemAccess.h" #include "MiscUtils.h" -#include "ModuleFactory.h" #include "Types.h" #include "VersionInfo.h" @@ -2027,6 +2026,9 @@ int32_t Units::getFocusPenalty(df::unit* unit, need_type_set need_types) { CHECK_NULL_POINTER(unit); int max_penalty = INT_MAX; + if (!unit->status.current_soul) { + return max_penalty; + } auto& needs = unit->status.current_soul->personality.needs; for (auto const need : needs) { if (need_types.test(need->id)) { diff --git a/library/modules/World.cpp b/library/modules/World.cpp index b41c625f17..d19f61fcee 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -223,16 +223,16 @@ int32_t World::GetCurrentSiteId() { DEBUG(world).print("searching for adventure site\n"); auto & world_map = world->map; auto adv_pos = Units::getPosition(adv); - DEBUG(world).print("adv_pos: (%d, %d, %d)\n", adv_pos.x, adv_pos.y, adv_pos.z); + DEBUG(world).print("adv_pos: ({}, {}, {})\n", adv_pos.x, adv_pos.y, adv_pos.z); df::coord2d rgn_pos(world_map.region_x + adv_pos.x/48, world_map.region_y + adv_pos.y/48); for (auto site : world->world_data->sites) { - DEBUG(world).print("scanning site %d: %s\n", site->id, Translation::translateName(&site->name, true).c_str()); - DEBUG(world).print(" rgn_pos: (%d, %d); site bounds: (%d, %d), (%d, %d) \n", + DEBUG(world).print("scanning site {}: {}\n", site->id, Translation::translateName(&site->name, true)); + DEBUG(world).print(" rgn_pos: ({}, {}); site bounds: ({}, {}), ({}, {}) \n", rgn_pos.x, rgn_pos.y, site->global_min_x, site->global_min_y, site->global_max_x, site->global_max_y); if (rgn_pos.x >= site->global_min_x && rgn_pos.x <= site->global_max_x && rgn_pos.y >= site->global_min_y && rgn_pos.y <= site->global_max_y) { - DEBUG(world).print("found site: %d\n", site->id); + DEBUG(world).print("found site: {}\n", site->id); return site->id; } } diff --git a/library/xml b/library/xml index 7b691d256f..a06e4c1c81 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 7b691d256f9427036e7ff24fa795a0f9334739e7 +Subproject commit a06e4c1c81f94eaf0d735cfe5a8ed2c64e0bcb81 diff --git a/package/launchdf.cpp b/package/launchdf.cpp index 5a850c6cf9..3b07abf99a 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -10,6 +10,9 @@ #include "steam_api.h" #include +#include +#include +#include #define xstr(s) str(s) #define str(s) #s @@ -234,10 +237,85 @@ bool waitForDF(bool nowait) { #endif +constexpr const char* old_filelist[] { + "hack", + "stonesense", +#ifdef WIN32 + "binpatch.exe", + "dfhack-run.exe", + "allegro-5.2.dll", + "allegro_color-5.2.dll", + "allegro_font-5.2.dll", + "allegro_image-5.2.dll", + "allegro_primitives-5.2.dll", + "allegro_ttf-5.2.dll", + "allegro-5.2.dll", + "dfhack-client.dll", + "dfhooks_dfhack.dll", + "lua53.dll", + "protobuf-lite.dll" +#else + "binpatch", + "dfhack-run", + "liballegro-5.2.so", + "liballegro_color-5.2.so", + "liballegro_font-5.2.so", + "liballegro_image-5.2.so", + "liballegro_primitives-5.2.so", + "liballegro_ttf-5.2.so", + "liballegro-5.2.so", + "libdfhack-client.so", + "libdfhooks_dfhack.so", + "liblua53.so", + "libprotobuf-lite.so" +#endif +}; + +bool check_for_old_install(std::filesystem::path df_path) +{ + for (auto file : old_filelist) + { + std::filesystem::path p = df_path / file; + if (std::filesystem::exists(p)) + return true; + } + return false; +} + +void remove_old_install(std::filesystem::path df_path) +{ + std::string message{ + "Removing legacy files:" + }; + + for (auto file : old_filelist) + { + std::error_code ec; + + std::filesystem::path p = df_path / file; + + if (std::filesystem::is_directory(p)) + std::filesystem::remove_all(p, ec); + else if (std::filesystem::is_regular_file(p)) + std::filesystem::remove(p, ec); + else + continue; + + message += "\n" + p.string() + ": " + (ec ? "failed to remove - " + ec.message() : "removed successfully"); + } +#ifdef WIN32 + MessageBoxW(NULL, std::wstring(message.begin(), message.end()).c_str(), L"Legacy Install Cleanup", 0); +#endif +} + #ifdef WIN32 int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) { #else int main(int argc, char* argv[]) { +#endif +#ifdef WIN32 + // force UTF-8 + std::setlocale(LC_ALL, ".utf8"); #endif // initialize steam context if (SteamAPI_RestartAppIfNecessary(DFHACK_STEAM_APPID)) { @@ -265,50 +343,136 @@ int main(int argc, char* argv[]) { } #ifdef WIN32 - if (is_running_on_wine()) { - // attempt launch via steam client - LPCWSTR err = launch_via_steam_posix(); - - if (err != NULL) - // steam client launch failed, attempt fallback launch - err = launch_direct(); - - if (err != NULL) - { - MessageBoxW(NULL, err, NULL, 0); - exit(1); - } - exit(0); - } + bool wine_detected = is_running_on_wine(); #endif - // steam detected and not running in wine + bool df_detected = SteamApps()->BIsAppInstalled(DF_STEAM_APPID); - if (!SteamApps()->BIsAppInstalled(DF_STEAM_APPID)) { + if (!df_detected) { // Steam DF is not installed. Assume DF is installed in same directory as DFHack and do a fallback launch exit(wrap_launch(launch_direct) ? 0 : 1); } - // obtain DF app path + // obtain DF and DFHack app paths - char buf[2048] = ""; + auto get_app_path_from_steam = [] (AppId_t appid) -> std::optional { + constexpr auto BUFSIZE = 2048; + char buf[BUFSIZE] = ""; + int bytes = SteamApps()->GetAppInstallDir(appid, (char*)&buf, BUFSIZE); + if (bytes <= 0) + return std::nullopt; + // steam API includes one or more null terminators after the path, so trim those off + for (; bytes > 0 && buf[bytes-1] == '\0'; bytes--); + return std::string(buf, bytes); + }; - int b1 = SteamApps()->GetAppInstallDir(DFHACK_STEAM_APPID, (char*)&buf, 2048); - std::string dfhack_install_folder = (b1 != -1) ? std::string(buf) : ""; + auto opt_dfhack_install_folder = get_app_path_from_steam(DFHACK_STEAM_APPID); + auto opt_df_install_folder = get_app_path_from_steam(DF_STEAM_APPID); + + if (opt_dfhack_install_folder && opt_df_install_folder && (*opt_df_install_folder != *opt_dfhack_install_folder)) + { + auto& dfhack_install_folder = *opt_dfhack_install_folder; + auto& df_install_folder = *opt_df_install_folder; - int b2 = SteamApps()->GetAppInstallDir(DF_STEAM_APPID, (char*)&buf, 2048); - std::string df_install_folder = (b2 != -1) ? std::string(buf) : ""; +#ifdef WIN32 + constexpr auto dfhooks_dll_name = "dfhooks.dll"; + constexpr auto dfhook_dfhack_dll_name = "dfhooks_dfhack.dll"; +#else + constexpr auto dfhooks_dll_name = "libdfhooks.so"; + constexpr auto dfhook_dfhack_dll_name = "libdfhooks_dfhack.so"; +#endif + // DF and DFHack are not co-installed (modern case) + // inject dfhooks.dll and dfhooks_dfhack.ini into DF install folder + std::filesystem::path dfhooks_dll_src = dfhack_install_folder / dfhooks_dll_name; + std::filesystem::path dfhooks_dll_dst = df_install_folder / dfhooks_dll_name; + std::filesystem::path dfhooks_ini_dst = df_install_folder / "dfhooks_dfhack.ini"; + std::filesystem::path dfhooks_dfhack_dll_src = dfhack_install_folder / "hack" / dfhook_dfhack_dll_name; + std::error_code ec; - if (df_install_folder != dfhack_install_folder) { - // DF and DFHack are not installed in the same library + std::filesystem::copy(dfhooks_dll_src, dfhooks_dll_dst, std::filesystem::copy_options::update_existing, ec); + if (!ec) + { + std::string indirection; + if (std::filesystem::exists(dfhooks_ini_dst)) + { + std::ifstream ini(dfhooks_ini_dst); + std::getline(ini, indirection); + } + + if (indirection != dfhooks_dfhack_dll_src.string()) + { + std::ofstream ini(dfhooks_ini_dst); + ini << dfhooks_dfhack_dll_src.string() << std::endl; + } + } + else + { #ifdef WIN32 - MessageBoxW(NULL, L"DFHack and Dwarf Fortress must be installed in the same Steam library.\nAborting.", NULL, 0); + std::wstring message{ + L"Failed to inject DFHack into Dwarf Fortress\n\n" + L"Details:\n" + dfhooks_dll_src.wstring() + + L" -> " + dfhooks_dll_dst.wstring() + + L"\n\nError code: " + std::to_wstring(ec.value()) + + L"\nError message: " + std::filesystem::relative(ec.message()).wstring() + }; + + MessageBoxW(NULL, message.c_str(), NULL, 0); #else - notify("DFHack and Dwarf Fortress must be installed in the same Steam library.\nAborting."); + std::string message{ + "Failed to inject DFHack into Dwarf Fortress\n\n" + "Details:\n" + dfhooks_dll_src.string() + + " -> " + dfhooks_dll_dst.string() + + "\n\nError code: " + std::to_string(ec.value()) + + "\nError message: " + std::filesystem::relative(ec.message()).string() + }; + + notify(message.c_str()); #endif - exit(1); + exit(1); + } + bool dirty = check_for_old_install(df_install_folder); + if (dirty) + { +#ifdef WIN32 + int ok = MessageBoxW(NULL, L"A legacy install of DFHack has been detected in the Dwarf Fortress folder. This likely means that you have installed DFHack with the old Steam client (or manually). This legacy installation will almost certainly interfere with using DFHack. Do you want to remove the old files now? (recommended)", L"Legacy DFHack Install Detected", MB_OKCANCEL); + + if (ok == IDOK) + remove_old_install(df_install_folder); +#else + std::string filelist; + for (auto file : old_filelist) + if (std::filesystem::exists(df_install_folder / file)) + filelist += (filelist.empty() ? "" : std::string(",")) + file; + + std::string message{ + "A legacy install of DFHack has been detected in the Dwarf Fortress directory.This likely means that you have installed DFHack with the old Steam client (or manually).This installation will almost certainly interfere with using DFHack. \n\n" + "To remove these files, run the following command: rm -r " + df_install_folder.string() + "/{ " + filelist + "}\n\n" + }; + + notify(message.c_str()); +#endif + } + } + +#ifdef WIN32 + if (wine_detected) + { + // attempt launch via steam client + LPCWSTR err = launch_via_steam_posix(); + + if (err != NULL) + // steam client launch failed, attempt fallback launch + err = launch_direct(); + + if (err != NULL) + { + MessageBoxW(NULL, err, NULL, 0); + exit(1); + } + exit(0); } +#endif if (!wrap_launch(launch_via_steam)) exit(1); @@ -329,6 +493,5 @@ int main(int argc, char* argv[]) { usleep(1000000); #endif } - exit(0); } diff --git a/plugins/3dveins.cpp b/plugins/3dveins.cpp index 8f75e0549e..07c1ca795d 100644 --- a/plugins/3dveins.cpp +++ b/plugins/3dveins.cpp @@ -433,13 +433,13 @@ struct GeoLayer void print_mineral_stats(color_ostream &out) { for (auto it = mineral_count.begin(); it != mineral_count.end(); ++it) - INFO(process, out).print("3dveins: %s %s: %d (%f)\n", - MaterialInfo(0, it->first.first).getToken().c_str(), - ENUM_KEY_STR(inclusion_type, it->first.second).c_str(), + INFO(process, out).print("3dveins: {} {}: {} ({})\n", + MaterialInfo(0, it->first.first).getToken(), + ENUM_KEY_STR(inclusion_type, it->first.second), it->second, (float(it->second) / unmined_tiles)); - INFO(process, out).print ("3dveins: Total tiles: %d (%d unmined)\n", tiles, unmined_tiles); + INFO(process, out).print ("3dveins: Total tiles: {} ({} unmined)\n", tiles, unmined_tiles); } bool form_veins(color_ostream &out); @@ -471,12 +471,12 @@ struct GeoBiome void print_mineral_stats(color_ostream &out) { - INFO(process,out).print("3dveins: Geological biome %d:\n", info.geo_index); + INFO(process,out).print("3dveins: Geological biome {}:\n", info.geo_index); for (size_t i = 0; i < layers.size(); i++) if (layers[i]) { - INFO(process, out).print("3dveins: Layer %ld\n", i); + INFO(process, out).print("3dveins: Layer {}\n", i); layers[i]->print_mineral_stats(out); } } @@ -590,7 +590,7 @@ bool VeinGenerator::init_biomes() if (info.geo_index < 0 || !info.geobiome) { - WARN(process, out).print("Biome %zd is not defined.\n", i); + WARN(process, out).print("Biome {} is not defined.\n", i); return false; } @@ -801,7 +801,7 @@ bool VeinGenerator::scan_layer_depth(Block *b, df::coord2d column, int z) { if (z != min_level[idx]-1 && min_level[idx] <= top_solid) { - WARN(process, out).print("Discontinuous layer %d at (%d,%d,%d).\n", + WARN(process, out).print("Discontinuous layer {} at ({} {} {}).\n", layer->index, x+column.x*16, y+column.y*16, z ); return false; @@ -852,7 +852,7 @@ bool VeinGenerator::adjust_layer_depth(df::coord2d column) if (max_level[i+1] != min_level[i]-1) { WARN(process, out).print( - "Gap or overlap with next layer %d at (%d,%d,%d-%d).\n", + "Gap or overlap with next layer {} at ({} {} {}-{}).\n", i+1, x+column.x*16, y+column.y*16, max_level[i+1], min_level[i] ); return false; @@ -895,7 +895,7 @@ bool VeinGenerator::adjust_layer_depth(df::coord2d column) } WARN(process, out).print( - "Layer height change in layer %d at (%d,%d,%d): %d instead of %d.\n", + "Layer height change in layer {} at ({} {} {}): {} instead of {}.\n", i, x+column.x*16, y+column.y*16, max_level[i], size, biome->layers[i]->thickness ); @@ -936,7 +936,7 @@ bool VeinGenerator::scan_block_tiles(Block *b, df::coord2d column, int z) if (unsigned(key.first) >= materials.size() || unsigned(key.second) >= NUM_INCLUSIONS) { - WARN(process, out).print("Invalid vein code: %d %d - aborting.\n",key.first,key.second); + WARN(process, out).print("Invalid vein code: {} {} - aborting.\n",key.first,ENUM_AS_STR(key.second)); return false; } @@ -946,9 +946,9 @@ bool VeinGenerator::scan_block_tiles(Block *b, df::coord2d column, int z) { // Report first occurence of unreasonable vein spec WARN(process, out).print( - "Unexpected vein %s %s - ", - MaterialInfo(0,key.first).getToken().c_str(), - ENUM_KEY_STR(inclusion_type, key.second).c_str() + "Unexpected vein {} {} - ", + MaterialInfo(0,key.first).getToken(), + ENUM_KEY_STR(inclusion_type, key.second) ); status = materials[key.first].default_type; @@ -956,8 +956,8 @@ bool VeinGenerator::scan_block_tiles(Block *b, df::coord2d column, int z) WARN(process, out).print("will be left in place.\n"); else WARN(process, out).print( - "correcting to %s.\n", - ENUM_KEY_STR(inclusion_type, df::inclusion_type(status)).c_str() + "correcting to {}.\n", + ENUM_KEY_STR(inclusion_type, df::inclusion_type(status)) ); } @@ -1095,7 +1095,7 @@ void VeinGenerator::write_block_tiles(Block *b, df::coord2d column, int z) if (!ok) { WARN(process, out).print( - "Couldn't write %d vein at (%d,%d,%d)\n", + "Couldn't write {} vein at ({} {} {})\n", mat, x+column.x*16, y+column.y*16, z ); } @@ -1285,7 +1285,7 @@ bool GeoLayer::form_veins(color_ostream &out) if (parent_id >= (int)refs.size()) { - WARN(process, out).print("Forward vein reference in biome %d.\n", biome->info.geo_index); + WARN(process, out).print("Forward vein reference in biome {}.\n", biome->info.geo_index); return false; } @@ -1306,10 +1306,10 @@ bool GeoLayer::form_veins(color_ostream &out) ctx = "only be in "+MaterialInfo(0,vptr->parent_mat()).getToken(); WARN(process, out).print( - "Duplicate vein %s %s in biome %d layer %d - will %s.\n", - MaterialInfo(0,key.first).getToken().c_str(), - ENUM_KEY_STR(inclusion_type, key.second).c_str(), - biome->info.geo_index, index, ctx.c_str() + "Duplicate vein {} {} in biome {} layer {} - will {}.\n", + MaterialInfo(0,key.first).getToken(), + ENUM_KEY_STR(inclusion_type, key.second), + biome->info.geo_index, index, ctx ); } @@ -1362,9 +1362,9 @@ bool VeinGenerator::place_orphan(t_veinkey key, int size, GeoLayer *from) if (best.empty()) { WARN(process,out).print( - "Could not place orphaned vein %s %s anywhere.\n", - MaterialInfo(0,key.first).getToken().c_str(), - ENUM_KEY_STR(inclusion_type, key.second).c_str() + "Could not place orphaned vein {} {} anywhere.\n", + MaterialInfo(0,key.first).getToken(), + ENUM_KEY_STR(inclusion_type, key.second) ); return true; @@ -1396,9 +1396,9 @@ bool VeinGenerator::place_orphan(t_veinkey key, int size, GeoLayer *from) if (size > 0) { WARN(process, out).print( - "Could not place all of orphaned vein %s %s: %d left.\n", - MaterialInfo(0,key.first).getToken().c_str(), - ENUM_KEY_STR(inclusion_type, key.second).c_str(), + "Could not place all of orphaned vein {} {}: {} left.\n", + MaterialInfo(0,key.first).getToken(), + ENUM_KEY_STR(inclusion_type, key.second), size ); } @@ -1546,8 +1546,8 @@ bool VeinGenerator::place_veins(bool verbose) if (!isStoneInorganic(key.first)) { WARN(process, out).print( - "Invalid vein material: %s\n", - MaterialInfo(0, key.first).getToken().c_str() + "Invalid vein material: {}\n", + MaterialInfo(0, key.first).getToken() ); return false; @@ -1555,7 +1555,7 @@ bool VeinGenerator::place_veins(bool verbose) if (!is_valid_enum_item(key.second)) { - WARN(process, out).print("Invalid vein type: %d\n", key.second); + WARN(process, out).print("Invalid vein type: {}\n", ENUM_AS_STR(key.second)); return false; } @@ -1568,16 +1568,16 @@ bool VeinGenerator::place_veins(bool verbose) sort(queue.begin(), queue.end(), vein_cmp); // Place tiles - TRACE(process,out).print("Processing... (%zu)", queue.size()); + TRACE(process,out).print("Processing... ({})", queue.size()); for (size_t j = 0; j < queue.size(); j++) { if (queue[j]->parent && !queue[j]->parent->placed) { WARN(process, out).print( - "\nParent vein not placed for %s %s.\n", - MaterialInfo(0,queue[j]->vein.first).getToken().c_str(), - ENUM_KEY_STR(inclusion_type, queue[j]->vein.second).c_str() + "\nParent vein not placed for {} {}.\n", + MaterialInfo(0,queue[j]->vein.first).getToken(), + ENUM_KEY_STR(inclusion_type, queue[j]->vein.second) ); return false; @@ -1591,16 +1591,16 @@ bool VeinGenerator::place_veins(bool verbose) } TRACE(process, out).print( - "\nVein layer %zu of %zu: %s %s (%.2f%%)... ", + "\nVein layer {} of {}: {} {} ({:.2})... ", j+1, queue.size(), - MaterialInfo(0,queue[j]->vein.first).getToken().c_str(), - ENUM_KEY_STR(inclusion_type, queue[j]->vein.second).c_str(), + MaterialInfo(0,queue[j]->vein.first).getToken(), + ENUM_KEY_STR(inclusion_type, queue[j]->vein.second), queue[j]->density() * 100 ); } else { - TRACE(process, out).print("\rVein layer %zu of %zu... ", j+1, queue.size()); + TRACE(process, out).print("\rVein layer {} of {}... ", j+1, queue.size()); } queue[j]->place_tiles(); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 1f721ec713..c898e5a7b0 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -27,6 +27,10 @@ set_source_files_properties( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE ) # Plugins # If you are adding a plugin that you do not intend to commit to the DFHack repo, # see instructions for adding "external" plugins at the end of this file. +# +# CAVEAT: currently (June 2026) DFHack's core code will only autoload plugins from +# the hack/plugins directory. They cannot be autoloaded from mod directories. +# This has been explicit policy for several years. # Example plugin that uses protobufs # proto file must be in the proto/ folder @@ -75,6 +79,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(dwarfvet dwarfvet.cpp LINK_LIBRARIES lua) #dfhack_plugin(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) #add_subdirectory(embark-assistant) + dfhack_plugin(edgescroll edgescroll.cpp) dfhack_plugin(eventful eventful.cpp LINK_LIBRARIES lua) dfhack_plugin(fastdwarf fastdwarf.cpp) dfhack_plugin(filltraffic filltraffic.cpp) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index 3726b86c7c..192662bccc 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -124,7 +124,7 @@ macro(dfhack_plugin) target_include_directories(${PLUGIN_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/proto") target_link_libraries(${PLUGIN_NAME} protobuf-lite) endif() - target_link_libraries(${PLUGIN_NAME} dfhack dfhack-version ${PLUGIN_LINK_LIBRARIES}) + target_link_libraries(${PLUGIN_NAME} dfhack dfhack-version ${FMTLIB} ${PLUGIN_LINK_LIBRARIES}) if(UNIX) set(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} ${PLUGIN_COMPILE_FLAGS_GCC}") @@ -141,6 +141,10 @@ macro(dfhack_plugin) set_target_properties(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.dll) endif() + if (UNIX) + set_target_properties(${PLUGIN_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN/..") + endif() + install(TARGETS ${PLUGIN_NAME} LIBRARY DESTINATION ${DFHACK_PLUGIN_DESTINATION} RUNTIME DESTINATION ${DFHACK_PLUGIN_DESTINATION}) diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp index 0ece73d982..a2685983fb 100644 --- a/plugins/add-spatter.cpp +++ b/plugins/add-spatter.cpp @@ -83,7 +83,7 @@ static void find_material(int *type, int *index, df::item *input, MaterialSource if (!info.findProduct(info, mat.product_name)) { color_ostream_proxy out(Core::getInstance().getConsole()); - out.printerr("Cannot find product '%s'\n", mat.product_name.c_str()); + out.printerr("Cannot find product '{}'\n", mat.product_name); } } @@ -293,7 +293,7 @@ static void find_reagent( return; } - out.printerr("Invalid reagent name '%s' in '%s'\n", name.c_str(), react->code.c_str()); + out.printerr("Invalid reagent name '{}' in '{}'\n", name, react->code); } static void parse_product( diff --git a/plugins/aquifer.cpp b/plugins/aquifer.cpp index 124fa29838..afb8bbe54f 100644 --- a/plugins/aquifer.cpp +++ b/plugins/aquifer.cpp @@ -35,7 +35,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector ¶meters) { if (!Core::getInstance().isMapLoaded()) { - out.printerr("Cannot run %s without a loaded map.\n", plugin_name); + out.printerr("Cannot run {} without a loaded map.\n", plugin_name); return CR_FAILURE; } @@ -142,7 +142,7 @@ static int get_max_aq_z(color_ostream &out, const df::coord &pos1, const df::coo static void get_z_range(color_ostream &out, int & minz, int & maxz, const df::coord & pos1, const df::coord & pos2, int levels, bool top_is_aq = false, int skip_top = 0) { - DEBUG(log,out).print("get_z_range: top_is_aq=%d, skip_top=%d\n", top_is_aq, skip_top); + DEBUG(log,out).print("get_z_range: top_is_aq={}, skip_top={}\n", top_is_aq, skip_top); if (!top_is_aq) maxz = get_max_ground_z(out, pos1, pos2) - skip_top; @@ -151,12 +151,12 @@ static void get_z_range(color_ostream &out, int & minz, int & maxz, const df::co minz = std::max((int)pos1.z, maxz - levels + 1); - DEBUG(log,out).print("calculated z range: minz=%d, maxz=%d\n", minz, maxz); + DEBUG(log,out).print("calculated z range: minz={}, maxz={}\n", minz, maxz); } static void aquifer_list(color_ostream &out, df::coord pos1, df::coord pos2, int levels, bool leaky) { - DEBUG(log,out).print("entering aquifer_list: pos1=%d,%d,%d, pos2=%d,%d,%d, levels=%d, leaky=%d\n", - pos1.x, pos1.y, pos1.z, pos2.x, pos2.y, pos2.z, levels, leaky); + DEBUG(log,out).print("entering aquifer_list: pos1={}, pos2={}, levels={}, leaky={}\n", + pos1, pos2, levels, leaky); std::map> light_tiles, heavy_tiles; @@ -173,14 +173,14 @@ static void aquifer_list(color_ostream &out, df::coord pos1, df::coord pos2, int }); if (light_tiles.empty() && heavy_tiles.empty()) { - out.print("No %saquifer tiles in the specified range.\n", leaky ? "leaking " : ""); + out.print("No {}aquifer tiles in the specified range.\n", leaky ? "leaking " : ""); } else { int elev_off = world->map.region_z - 100; for (int z = maxz; z >= minz; --z) { int lcount = light_tiles.contains(z) ? light_tiles[z] : 0; int hcount = heavy_tiles.contains(z) ? heavy_tiles[z] : 0; if (lcount || hcount) - out.print("z-level %3d (elevation %4d) has %6d %slight aquifer tile(s) and %6d %sheavy aquifer tile(s)\n", + out.print("z-level {} (elevation {}) has {} {}light aquifer tile(s) and {} {}heavy aquifer tile(s)\n", z, z+elev_off, lcount, leaky ? "leaking " : "", hcount, leaky ? "leaking " : ""); } } @@ -189,22 +189,21 @@ static void aquifer_list(color_ostream &out, df::coord pos1, df::coord pos2, int static int aquifer_drain(color_ostream &out, string aq_type, df::coord pos1, df::coord pos2, int skip_top, int levels, bool leaky) { - DEBUG(log,out).print("entering aquifer_drain: aq_type=%s, pos1=%d,%d,%d, pos2=%d,%d,%d," - " skip_top=%d, levels=%d, leaky=%d\n", aq_type.c_str(), - pos1.x, pos1.y, pos1.z, pos2.x, pos2.y, pos2.z, skip_top, levels, leaky); + DEBUG(log,out).print("entering aquifer_drain: aq_type={}, pos1={}, pos2={}, skip_top={}, levels={}, leaky={}\n", + aq_type, pos1, pos2, skip_top, levels, leaky); const bool all = aq_type == "all"; const bool heavy_state = aq_type == "heavy"; int modified = Maps::removeAreaAquifer(pos1, pos2, [&](df::coord pos, df::map_block* block) -> bool { - TRACE(log, out).print("examining tile: pos=%d,%d,%d\n", pos.x, pos.y, pos.z); + TRACE(log, out).print("examining tile: pos={}\n", pos); return Maps::isTileAquifer(pos) && (all || Maps::isTileHeavyAquifer(pos) == heavy_state) && (!leaky || is_leaky(pos)); }); - DEBUG(log, out).print("drained aquifer tiles in area: pos1=%d,%d,%d, pos2=%d,%d,%d, heavy_state=%d, all=%d, count=%d\n", - pos1.x, pos1.y, pos1.z, pos2.x, pos2.y, pos2.z, heavy_state, all, modified); + DEBUG(log, out).print("drained aquifer tiles in area: pos1={}, pos2={}, heavy_state={}, all={}, count={}\n", + pos1, pos2, heavy_state, all, modified); return modified; } @@ -212,21 +211,20 @@ static int aquifer_drain(color_ostream &out, string aq_type, static int aquifer_convert(color_ostream &out, string aq_type, df::coord pos1, df::coord pos2, int skip_top, int levels, bool leaky) { - DEBUG(log,out).print("entering aquifer_convert: aq_type=%s, pos1=%d,%d,%d, pos2=%d,%d,%d," - " skip_top=%d, levels=%d, leaky=%d\n", aq_type.c_str(), - pos1.x, pos1.y, pos1.z, pos2.x, pos2.y, pos2.z, skip_top, levels, leaky); + DEBUG(log,out).print("entering aquifer_convert: aq_type={}, pos1={}, pos2={}, skip_top={}, levels={}, leaky={}\n", + aq_type, pos1, pos2, skip_top, levels, leaky); const bool heavy_state = aq_type == "heavy"; int modified = Maps::setAreaAquifer(pos1, pos2, heavy_state, [&](df::coord pos, df::map_block* block) -> bool { - TRACE(log, out).print("examining tile: pos=%d,%d,%d\n", pos.x, pos.y, pos.z); + TRACE(log, out).print("examining tile: pos={}\n", pos); return Maps::isTileAquifer(pos) && Maps::isTileHeavyAquifer(pos) != heavy_state && (!leaky || is_leaky(pos)); }); - DEBUG(log, out).print("converted aquifer tiles in area: pos1=%d,%d,%d, pos2=%d,%d,%d, heavy_state=%d, count=%d\n", - pos1.x, pos1.y, pos1.z, pos2.x, pos2.y, pos2.z, heavy_state, modified); + DEBUG(log, out).print("converted aquifer tiles in area: pos1={}, pos2={}, heavy_state={}, count={}\n", + pos1, pos2, heavy_state, modified); return modified; } @@ -234,20 +232,19 @@ static int aquifer_convert(color_ostream &out, string aq_type, static int aquifer_add(color_ostream &out, string aq_type, df::coord pos1, df::coord pos2, int skip_top, int levels, bool leaky) { - DEBUG(log,out).print("entering aquifer_add: aq_type=%s, pos1=%d,%d,%d, pos2=%d,%d,%d," - " skip_top=%d, levels=%d, leaky=%d\n", aq_type.c_str(), - pos1.x, pos1.y, pos1.z, pos2.x, pos2.y, pos2.z, skip_top, levels, leaky); + DEBUG(log,out).print("entering aquifer_add: aq_type={}, pos1={}, pos2={}, skip_top={}, levels={}, leaky={}\n", + aq_type, pos1, pos2, skip_top, levels, leaky); const bool heavy_state = aq_type == "heavy"; int modified = Maps::setAreaAquifer(pos1, pos2, heavy_state, [&](df::coord pos, df::map_block* block) -> bool { - TRACE(log, out).print("examining tile: pos=%d,%d,%d\n", pos.x, pos.y, pos.z); + TRACE(log, out).print("examining tile: pos={}\n", pos); return (leaky || !is_leaky(pos)) && (!Maps::isTileAquifer(pos) || Maps::isTileHeavyAquifer(pos) != heavy_state); }); - DEBUG(log, out).print("added aquifer tiles in area: pos1=%d,%d,%d, pos2=%d,%d,%d, heavy_state=%d, count=%d\n", - pos1.x, pos1.y, pos1.z, pos2.x, pos2.y, pos2.z, heavy_state, modified); + DEBUG(log, out).print("added aquifer tiles in area: pos1={}, pos2={}, heavy_state={}, count={}\n", + pos1, pos2, heavy_state, modified); return modified; } diff --git a/plugins/army-controller-sanity.cpp b/plugins/army-controller-sanity.cpp index 089b626b5f..1924cfc9e9 100644 --- a/plugins/army-controller-sanity.cpp +++ b/plugins/army-controller-sanity.cpp @@ -54,12 +54,12 @@ namespace { for (auto ac : ent->army_controllers) { if (ac_set.count(ac) == 0) { - WARN(log).print("acValidationError: Bad controller %p found in entity id %d\n", ac, ent->id); + WARN(log).print("acValidationError: Bad controller {} found in entity id {}\n", static_cast(ac), ent->id); ok = false; } if (ac_set.count(ac) != 0 && ac->entity_id != ent->id) { - WARN(log).print("acValidationError: Army controller %d has entity id %d but is linked from entity with id %d\n", ac->id, ac->entity_id, ent->id); + WARN(log).print("acValidationError: Army controller {} has entity id {} but is linked from entity with id {}\n", ac->id, ac->entity_id, ent->id); } } } @@ -68,17 +68,17 @@ namespace { { auto ac = ar->controller; if (ac && ac_set.count(ac) == 0) { - WARN(log).print("acValidationError: Bad controller %p found in army id %d\n", ac, ar->id); + WARN(log).print("acValidationError: Bad controller {} found in army id {}\n", static_cast(ac), ar->id); ok = false; } else if (ac && ac->id != ar->controller_id) { - WARN(log).print("acValidationError: controller %p id mismatch (%d != %d) in army %d\n", ac, ar->controller_id, ac->id, ar->id); + WARN(log).print("acValidationError: controller {} id mismatch ({} != {}) in army {}\n", static_cast(ac), ar->controller_id, ac->id, ar->id); ok = false; } else if (!ac && ar->controller_id != -1) { - WARN(log).print("acValidationError: army %d has nonzero controller %d but controller pointer is null\n", ar->id, ar->controller_id); + WARN(log).print("acValidationError: army {} has nonzero controller {} but controller pointer is null\n", ar->id, ar->controller_id); ok = false; } } @@ -87,17 +87,17 @@ namespace { { auto ac = un->enemy.army_controller; if (ac && ac_set.count(ac) == 0) { - WARN(log).print("acValidationError: Bad controller %p found in unit id %d\n", ac, un->id); + WARN(log).print("acValidationError: Bad controller {} found in unit id {}\n", static_cast(ac), un->id); ok = false; } else if (ac && ac->id != un->enemy.army_controller_id) { - WARN(log).print("acValidationError: controller %p id mismatch (%d != %d) in unit %d\n", ac, un->enemy.army_controller_id, ac->id, un->id); + WARN(log).print("acValidationError: controller {} id mismatch ({} != {}) in unit {}\n", static_cast(ac), un->enemy.army_controller_id, ac->id, un->id); ok = false; } else if (!ac && un->enemy.army_controller_id != -1) { - WARN(log).print("acValidationError: unit %d has has nonzero controller %d but controller pointer is null\n", un->id, un->enemy.army_controller_id); + WARN(log).print("acValidationError: unit {} has has nonzero controller {} but controller pointer is null\n", un->id, un->enemy.army_controller_id); ok = false; } } @@ -105,7 +105,7 @@ namespace { last_army_controller_next_id = *army_controller_next_id; last_ac_vec_size = world->army_controllers.all.size(); - INFO(log).print("acValidation: controller count = %ld, next id = %d, season tick count = %d\n", + INFO(log).print("acValidation: controller count = {}, next id = {}, season tick count = {}\n", last_ac_vec_size, last_army_controller_next_id, *cur_year_tick); return ok; diff --git a/plugins/autobutcher.cpp b/plugins/autobutcher.cpp index 8fde1cc347..8f7bbb16fa 100644 --- a/plugins/autobutcher.cpp +++ b/plugins/autobutcher.cpp @@ -82,13 +82,13 @@ DFhackCExport command_result plugin_init(color_ostream &out, vector fortress_age > 0) autobutcher_cycle(out); } else { - DEBUG(control,out).print("%s from the API, but already %s; no action\n", + DEBUG(control,out).print("{} from the API, but already {}; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); } @@ -104,7 +104,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { } DFhackCExport command_result plugin_shutdown (color_ostream &out) { - DEBUG(control,out).print("shutting down %s\n", plugin_name); + DEBUG(control,out).print("shutting down {}\n", plugin_name); cleanup_autobutcher(out); return CR_OK; } @@ -128,7 +128,7 @@ DFhackCExport command_result plugin_load_site_data (color_ostream &out) { // all the other state we can directly read/modify from the persistent // data structure. is_enabled = config.get_bool(CONFIG_IS_ENABLED); - DEBUG(control,out).print("loading persisted enabled state: %s\n", + DEBUG(control,out).print("loading persisted enabled state: {}\n", is_enabled ? "true" : "false"); // load the persisted watchlist @@ -140,7 +140,7 @@ DFhackCExport command_result plugin_load_site_data (color_ostream &out) { DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { if (event == DFHack::SC_WORLD_UNLOADED) { if (is_enabled) { - DEBUG(control,out).print("world unloaded; disabling %s\n", + DEBUG(control,out).print("world unloaded; disabling {}\n", plugin_name); is_enabled = false; } @@ -209,8 +209,8 @@ static bool isHighPriority(df::unit *unit) { } static void doMarkForSlaughter(df::unit *unit) { - DEBUG(cycle).print("marking unit %d for slaughter: %s, %s, high priority: %s, age: %.2f\n", - unit->id, Units::getReadableName(unit).c_str(), + DEBUG(cycle).print("marking unit {} for slaughter: {}, {}, high priority: {}, age: {:.2f}\n", + unit->id, Units::getReadableName(unit), Units::isFemale(unit) ? "female" : "male", isHighPriority(unit) ? "yes" : "no", Units::getAge(unit)); @@ -266,7 +266,7 @@ struct WatchedRace { fk_prot(0), fa_prot(0), mk_prot(0), ma_prot(0), fk_units(compareKids), mk_units(compareKids), fa_units(compareAdults), ma_units(compareAdults) { - TRACE(control,out).print("creating new WatchedRace: id=%d, watched=%s, fk=%u, mk=%u, fa=%u, ma=%u\n", + TRACE(control,out).print("creating new WatchedRace: id={}, watched={}, fk={}, mk={}, fa={}, ma={}\n", id, watch ? "true" : "false", fk, mk, fa, ma); } @@ -293,8 +293,8 @@ struct WatchedRace { rconfig.ival(5) = ma; } else { - ERR(control,out).print("could not create persistent key for race: %s", - Units::getRaceNameById(raceId).c_str()); + ERR(control,out).print("could not create persistent key for race: {}", + Units::getRaceNameById(raceId)); } } @@ -375,14 +375,14 @@ static void init_autobutcher(color_ostream &out) { vector watchlist; World::GetPersistentSiteData(&watchlist, WATCHLIST_CONFIG_KEY_PREFIX, true); for (auto & p : watchlist) { - DEBUG(control,out).print("Reading from save: %s\n", p.key().c_str()); + DEBUG(control,out).print("Reading from save: {}\n", p.key()); WatchedRace *w = new WatchedRace(out, p); watched_races.emplace(w->raceId, w); } } static void cleanup_autobutcher(color_ostream &out) { - DEBUG(control,out).print("cleaning %s state\n", plugin_name); + DEBUG(control,out).print("cleaning {} state\n", plugin_name); race_to_id.clear(); for (auto w : watched_races) delete w.second; @@ -396,7 +396,7 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o static command_result df_autobutcher(color_ostream &out, vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -508,7 +508,7 @@ static void autobutcher_control(color_ostream &out) { static void autobutcher_target(color_ostream &out, const autobutcher_options &opts) { if (opts.races_new) { - DEBUG(control,out).print("setting targets for new races to fk=%u, mk=%u, fa=%u, ma=%u\n", + DEBUG(control,out).print("setting targets for new races to fk={}, mk={}, fa={}, ma={}\n", opts.fk, opts.mk, opts.fa, opts.ma); config.set_int(CONFIG_DEFAULT_FK, opts.fk); config.set_int(CONFIG_DEFAULT_MK, opts.mk); @@ -517,7 +517,7 @@ static void autobutcher_target(color_ostream &out, const autobutcher_options &op } if (opts.races_all) { - DEBUG(control,out).print("setting targets for all races on watchlist to fk=%u, mk=%u, fa=%u, ma=%u\n", + DEBUG(control,out).print("setting targets for all races on watchlist to fk={}, mk={}, fa={}, ma={}\n", opts.fk, opts.mk, opts.fa, opts.ma); for (auto w : watched_races) { w.second->fk = opts.fk; @@ -530,7 +530,7 @@ static void autobutcher_target(color_ostream &out, const autobutcher_options &op for (auto race : opts.races) { if (!race_to_id.count(*race)) { - out.printerr("race not found: '%s'", race->c_str()); + out.printerr("race not found: '{}'", race->c_str()); continue; } int id = race_to_id[*race]; @@ -559,7 +559,7 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o for (auto race : opts.races) { if (!race_to_id.count(*race)) { - out.printerr("race not found: '%s'", race->c_str()); + out.printerr("race not found: '{}'", race->c_str()); continue; } ids.emplace(race_to_id[*race]); @@ -576,7 +576,7 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o config.get_int(CONFIG_DEFAULT_MA))); } else if (!watched_races[id]->isWatched) { - DEBUG(control,out).print("watching: %s\n", opts.command.c_str()); + DEBUG(control,out).print("watching: {}\n", opts.command); watched_races[id]->isWatched = true; } } @@ -590,13 +590,13 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o config.get_int(CONFIG_DEFAULT_MA))); } else if (watched_races[id]->isWatched) { - DEBUG(control,out).print("unwatching: %s\n", opts.command.c_str()); + DEBUG(control,out).print("unwatching: {}\n", opts.command); watched_races[id]->isWatched = false; } } else if (opts.command == "forget") { if (watched_races.count(id)) { - DEBUG(control,out).print("forgetting: %s\n", opts.command.c_str()); + DEBUG(control,out).print("forgetting: {}\n", opts.command); watched_races[id]->RemoveConfig(out); delete watched_races[id]; watched_races.erase(id); @@ -680,7 +680,7 @@ static void autobutcher_cycle(color_ostream &out) { // mark that we have recently run cycle_timestamp = world->frame_counter; - DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + DEBUG(cycle,out).print("running {} cycle\n", plugin_name); // check if there is anything to watch before walking through units vector if (!config.get_bool(CONFIG_AUTOWATCH)) { @@ -720,8 +720,8 @@ static void autobutcher_cycle(color_ostream &out) { w->UpdateConfig(out); watched_races.emplace(unit->race, w); - INFO(cycle,out).print("New race added to autobutcher watchlist: %s\n", - Units::getRaceNamePluralById(unit->race).c_str()); + INFO(cycle,out).print("New race added to autobutcher watchlist: {}\n", + Units::getRaceNamePluralById(unit->race)); } if (w->isWatched) { @@ -740,8 +740,8 @@ static void autobutcher_cycle(color_ostream &out) { if (slaughter_count) { std::stringstream ss; ss << slaughter_count; - INFO(cycle,out).print("%s marked for slaughter: %s\n", - Units::getRaceNamePluralById(w.first).c_str(), ss.str().c_str()); + INFO(cycle,out).print("{} marked for slaughter: {}\n", + Units::getRaceNamePluralById(w.first), ss.str()); } } } @@ -817,7 +817,7 @@ static bool autowatch_isEnabled() { } static void autowatch_setEnabled(color_ostream &out, bool enable) { - DEBUG(control,out).print("auto-adding to watchlist %s\n", enable ? "started" : "stopped"); + DEBUG(control,out).print("auto-adding to watchlist {}\n", enable ? "started" : "stopped"); config.set_bool(CONFIG_AUTOWATCH, enable); if (config.get_bool(CONFIG_IS_ENABLED)) autobutcher_cycle(out); @@ -843,7 +843,7 @@ static void autobutcher_setWatchListRace(color_ostream &out, unsigned id, unsign WatchedRace * w = new WatchedRace(out, id, watched, fk, mk, fa, ma); w->UpdateConfig(out); watched_races.emplace(id, w); - INFO(control,out).print("New race added to autobutcher watchlist: %s\n", + INFO(control,out).print("New race added to autobutcher watchlist: {}\n", Units::getRaceNamePluralById(id).c_str()); } diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 7c2cfc9a5e..811a3d1cb0 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -79,7 +79,7 @@ static PersistentDataItem & ensure_burrow_config(color_ostream &out, int id) { if (watched_burrows_indices.count(id)) return watched_burrows[watched_burrows_indices[id]]; string keyname = BURROW_CONFIG_KEY_PREFIX + int_to_string(id); - DEBUG(control,out).print("creating new persistent key for burrow %d\n", id); + DEBUG(control,out).print("creating new persistent key for burrow {}\n", id); watched_burrows.emplace_back(World::GetPersistentSiteData(keyname, true)); size_t idx = watched_burrows.size()-1; watched_burrows_indices.emplace(id, idx); @@ -88,7 +88,7 @@ static PersistentDataItem & ensure_burrow_config(color_ostream &out, int id) { static void remove_burrow_config(color_ostream &out, int id) { if (!watched_burrows_indices.count(id)) return; - DEBUG(control,out).print("removing persistent key for burrow %d\n", id); + DEBUG(control,out).print("removing persistent key for burrow {}\n", id); size_t idx = watched_burrows_indices[id]; World::DeletePersistentData(watched_burrows[idx]); watched_burrows.erase(watched_burrows.begin()+idx); @@ -110,7 +110,7 @@ static command_result do_command(color_ostream &out, vector ¶meters) static int32_t do_cycle(color_ostream &out, bool force_designate = false); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(control,out).print("initializing %s\n", plugin_name); + DEBUG(control,out).print("initializing {}\n", plugin_name); // provide a configuration interface for the plugin commands.push_back(PluginCommand( @@ -123,19 +123,19 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector frame_counter - cycle_timestamp >= CYCLE_TICKS) { int32_t designated = do_cycle(out); if (0 < designated) - out.print("autochop: designated %d tree(s) for chopping\n", designated); + out.print("autochop: designated {} tree(s) for chopping\n", designated); } return CR_OK; } static command_result do_command(color_ostream &out, vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -371,7 +371,7 @@ static int32_t scan_tree(color_ostream & out, df::plant *plant, int32_t *expecte map *designated_tree_counts, map &clearcut_burrows, map &chop_burrows) { - TRACE(cycle,out).print(" scanning tree at %d,%d,%d\n", + TRACE(cycle,out).print(" scanning tree at {},{},{}\n", plant->pos.x, plant->pos.y, plant->pos.z); if (!is_valid_tree(plant)) @@ -518,7 +518,7 @@ static void scan_logs(color_ostream &out, int32_t *usable_logs, *inaccessible_logs = 0; for (auto &item : world->items.other[items_other_id::IN_PLAY]) { - TRACE(cycle,out).print(" scanning log %d\n", item->id); + TRACE(cycle,out).print(" scanning log {}\n", item->id); if (item->flags.whole & bad_flags.whole) continue; @@ -538,7 +538,7 @@ static void scan_logs(color_ostream &out, int32_t *usable_logs, } static int32_t do_cycle(color_ostream &out, bool force_designate) { - DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + DEBUG(cycle,out).print("running {} cycle\n", plugin_name); // mark that we have recently run cycle_timestamp = world->frame_counter; @@ -580,7 +580,7 @@ static int32_t do_cycle(color_ostream &out, bool force_designate) { // of accessible trees int32_t needed = config.get_int(CONFIG_MAX_LOGS) - (usable_logs + expected_yield); - DEBUG(cycle,out).print("needed logs for this cycle: %d\n", needed); + DEBUG(cycle,out).print("needed logs for this cycle: {}\n", needed); for (auto & entry : designatable_trees_by_size) { if (!Designations::markPlant(entry.second)) continue; @@ -590,7 +590,7 @@ static int32_t do_cycle(color_ostream &out, bool force_designate) { return newly_marked; } } - out.print("autochop: insufficient accessible trees to reach log target! Still need %d logs!\n", + out.print("autochop: insufficient accessible trees to reach log target! Still need {} logs!\n", needed); return newly_marked; } @@ -623,8 +623,8 @@ static const char * get_protect_str(bool protect_brewable, bool protect_edible, static void autochop_printStatus(color_ostream &out) { DEBUG(control,out).print("entering autochop_printStatus\n"); validate_burrow_configs(out); - out.print("autochop is %s\n\n", is_enabled ? "enabled" : "disabled"); - out.print(" keeping log counts between %d and %d\n", + out.print("autochop is {}\n\n", is_enabled ? "enabled" : "disabled"); + out.print(" keeping log counts between {} and {}\n", config.get_int(CONFIG_MIN_LOGS), config.get_int(CONFIG_MAX_LOGS)); if (config.get_bool(CONFIG_WAITING_FOR_MIN)) out.print(" currently waiting for min threshold to be crossed before designating more trees\n"); @@ -643,19 +643,19 @@ static void autochop_printStatus(color_ostream &out) { &designated_trees, &accessible_yield, &tree_counts, &designated_tree_counts); out.print("summary:\n"); - out.print(" accessible logs (usable stock): %d\n", usable_logs); - out.print(" inaccessible logs: %d\n", inaccessible_logs); - out.print(" total visible logs: %d\n", usable_logs + inaccessible_logs); + out.print(" accessible logs (usable stock): {}\n", usable_logs); + out.print(" inaccessible logs: {}\n", inaccessible_logs); + out.print(" total visible logs: {}\n", usable_logs + inaccessible_logs); out.print("\n"); - out.print(" accessible trees: %d\n", accessible_trees); - out.print(" inaccessible trees: %d\n", inaccessible_trees); - out.print(" total visible trees: %d\n", accessible_trees + inaccessible_trees); + out.print(" accessible trees: {}\n", accessible_trees); + out.print(" inaccessible trees: {}\n", inaccessible_trees); + out.print(" total visible trees: {}\n", accessible_trees + inaccessible_trees); out.print("\n"); - out.print(" designated trees: %d\n", designated_trees); - out.print(" expected logs from designated trees: %d\n", expected_yield); - out.print(" expected logs from all accessible trees: %d\n", accessible_yield); + out.print(" designated trees: {}\n", designated_trees); + out.print(" expected logs from designated trees: {}\n", expected_yield); + out.print(" expected logs from all accessible trees: {}\n", accessible_yield); out.print("\n"); - out.print(" total trees harvested: %d\n", plotinfo->trees_removed); + out.print(" total trees harvested: {}\n", plotinfo->trees_removed); out.print("\n"); if (!plotinfo->burrows.list.size()) { @@ -669,11 +669,10 @@ static void autochop_printStatus(color_ostream &out) { for (auto &burrow : plotinfo->burrows.list) { name_width = std::max(name_width, (int)burrow->name.size()); } - name_width = -name_width; // left justify - const char *fmt = "%*s %4s %4s %8s %5s %6s %7s\n"; - out.print(fmt, name_width, "burrow name", " id ", "chop", "clearcut", "trees", "marked", "protect"); - out.print(fmt, name_width, "-----------", "----", "----", "--------", "-----", "------", "-------"); + constexpr auto fmt = "{:<{}} {:4} {:4} {:8} {:5} {:6} {:7}\n"; + out.print(fmt, "burrow name", name_width, " id ", "chop", "clearcut", "trees", "marked", "protect"); + out.print(fmt, "-----------", name_width, "----", "----", "--------", "-----", "------", "-------"); for (auto &burrow : plotinfo->burrows.list) { bool chop = false; @@ -689,17 +688,17 @@ static void autochop_printStatus(color_ostream &out) { protect_edible = c.get_bool(BURROW_CONFIG_PROTECT_EDIBLE); protect_cookable = c.get_bool(BURROW_CONFIG_PROTECT_COOKABLE); } - out.print(fmt, name_width, burrow->name.c_str(), int_to_string(burrow->id).c_str(), + out.print(fmt, Burrows::getName(burrow), name_width, burrow->id, chop ? "[x]" : "[ ]", clearcut ? "[x]" : "[ ]", - int_to_string(tree_counts[burrow->id]).c_str(), - int_to_string(designated_tree_counts[burrow->id]).c_str(), + tree_counts[burrow->id], + designated_tree_counts[burrow->id], get_protect_str(protect_brewable, protect_edible, protect_cookable)); } } static void autochop_designate(color_ostream &out) { DEBUG(control,out).print("entering autochop_designate\n"); - out.print("designated %d tree(s) for chopping\n", do_cycle(out, true)); + out.print("designated {} tree(s) for chopping\n", do_cycle(out, true)); } static void autochop_undesignate(color_ostream &out) { @@ -709,7 +708,7 @@ static void autochop_undesignate(color_ostream &out) { if (is_valid_tree(plant) && Designations::unmarkPlant(plant)) ++count; } - out.print("undesignated %d tree(s)\n", count); + out.print("undesignated {} tree(s)\n", count); } static void autochop_setTargets(color_ostream &out, int32_t max_logs, int32_t min_logs) { diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index a4516f4dce..d9b718f0ba 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -20,6 +20,7 @@ #include "df/item_helmst.h" #include "df/item_pantsst.h" #include "df/item_shoesst.h" +#include "df/itemdef_handlerst.h" #include "df/itemdef_armorst.h" #include "df/itemdef_glovesst.h" #include "df/itemdef_helmst.h" @@ -64,8 +65,8 @@ enum ConfigValues { struct ClothingRequirement; command_result autoclothing(color_ostream &out, vector ¶meters); static void do_autoclothing(); -static bool validateMaterialCategory(ClothingRequirement *requirement); -static bool setItem(string name, ClothingRequirement *requirement); +static bool validateMaterialCategory(ClothingRequirement& requirement); +static std::optional setItem(string name); static void generate_control(color_ostream &out); static bool isAvailableItem(df::item *item); @@ -87,11 +88,11 @@ struct ClothingRequirement { int16_t needed_per_citizen = 0; map total_needed_per_race; - bool matches(ClothingRequirement *b) { - return b->jobType == this->jobType - && b->itemType == this->itemType - && b->item_subtype == this->item_subtype - && b->material_category.whole == this->material_category.whole; + bool operator==(ClothingRequirement& b) const { + return b.jobType == this->jobType + && b.itemType == this->itemType + && b.item_subtype == this->item_subtype + && b.material_category.whole == this->material_category.whole; } string Serialize() { @@ -127,32 +128,41 @@ struct ClothingRequirement { stream >> needed_per_citizen; } - bool SetFromParameters(color_ostream &out, vector ¶meters) + //FIXME: when C++23, use std::expected here + static std::optional createFromParameters(color_ostream &out, vector ¶meters) { - if (parameters[0] == "clear") { // handle the clear case - if (!set_bitfield_field(&material_category, parameters[1], 1)) - out << "Unrecognized material type: " << parameters[1] << endl; - if (!setItem(parameters[2], this)) { - out << "Unrecognized item name or token: " << parameters[2] << endl; - return false; - } - else if (!validateMaterialCategory(this)) { - out << parameters[1] << " is not a valid material category for " << parameters[2] << endl; - return false; - } - return true; + df::job_material_category material_category; + if (parameters.size() < 1) return std::nullopt; + + size_t idx = 0; + if (parameters[0] == "clear") idx++; + + if (parameters.size() < idx + 1) return std::nullopt; + + if (!set_bitfield_field(&material_category, parameters[idx], 1)) + { + out << "Unrecognized material type: " << parameters[idx] << endl; + return std::nullopt; } - if (!set_bitfield_field(&material_category, parameters[0], 1)) - out << "Unrecognized material type: " << parameters[0] << endl; - if (!setItem(parameters[1], this)) { - out << "Unrecognized item name or token: " << parameters[1] << endl; - return false; + + auto req = setItem(parameters[idx + 1]); + + if (!req) + { + out << "Unrecognized item name or token: " << parameters[idx+1] << endl; + return std::nullopt; } - else if (!validateMaterialCategory(this)) { - out << parameters[0] << " is not a valid material category for " << parameters[1] << endl; - return false; + + req->material_category = material_category; + + if (!validateMaterialCategory(*req)) { + out << parameters[idx] << " is not a valid material category for " << parameters[idx+1] << endl; + return std::nullopt; + } + else + { + return req; } - return true; } string ToReadableLabel() { @@ -237,7 +247,7 @@ DFhackCExport command_result plugin_load_site_data(color_ostream &out) { } is_enabled = enabled.get_bool(CONFIG_IS_ENABLED); - DEBUG(control, out).print("loading persisted enabled state: %s\n", + DEBUG(control, out).print("loading persisted enabled state: {}\n", is_enabled ? "true" : "false"); // Parse constraints @@ -282,21 +292,21 @@ DFhackCExport command_result plugin_save_site_data(color_ostream &out) { DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } if (enable != is_enabled) { auto enabled = World::GetPersistentSiteData(CONFIG_KEY); is_enabled = enable; - DEBUG(control, out).print("%s from the API; persisting\n", + DEBUG(control, out).print("{} from the API; persisting\n", is_enabled ? "enabled" : "disabled"); enabled.set_bool(CONFIG_IS_ENABLED, is_enabled); if (enable) do_autoclothing(); } else { - DEBUG(control, out).print("%s from the API, but already %s; no action\n", + DEBUG(control, out).print("{} from the API, but already {}; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); } @@ -311,94 +321,98 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { return CR_OK; } -static bool setItemFromName(string name, ClothingRequirement *requirement) +std::optional setItemFromName(string name) { -#define SEARCH_ITEM_RAWS(rawType, job, item) \ -for (auto &itemdef : world->raws.itemdefs.rawType) { \ - string fullName = itemdef->adjective.empty() ? itemdef->name : itemdef->adjective + " " + itemdef->name; \ - if (fullName == name) { \ - requirement->jobType = job_type::job; \ - requirement->itemType = item_type::item; \ - requirement->item_subtype = itemdef->subtype; \ - return true; \ - } \ -} - SEARCH_ITEM_RAWS(armor, MakeArmor, ARMOR); - SEARCH_ITEM_RAWS(gloves, MakeGloves, GLOVES); - SEARCH_ITEM_RAWS(shoes, MakeShoes, SHOES); - SEARCH_ITEM_RAWS(helms, MakeHelm, HELM); - SEARCH_ITEM_RAWS(pants, MakePants, PANTS); - return false; + auto SEARCH_ITEM_RAWS = [&name](FT df::itemdef_handlerst:: * rawType, df::job_type job, df::item_type item) -> std::optional + { + auto& itemdefs = world->raws.itemdefs.*rawType; + auto it = std::find_if(itemdefs.begin(), itemdefs.end(), [&name] (auto& itemdef) { + string fullName = itemdef->adjective.empty() ? itemdef->name : itemdef->adjective + " " + itemdef->name; + return fullName == name; + }); + if (it != itemdefs.end()) + { + auto& itemdef = *it; + return ClothingRequirement{.jobType = job, .itemType = item, .item_subtype = itemdef->subtype}; + } + return std::nullopt; + }; + + if (auto v = SEARCH_ITEM_RAWS(&df::itemdef_handlerst::armor, df::job_type::MakeArmor, df::item_type::ARMOR)) + return v; + if (auto v = SEARCH_ITEM_RAWS(&df::itemdef_handlerst::gloves, df::job_type::MakeGloves, df::item_type::GLOVES)) + return v; + if (auto v = SEARCH_ITEM_RAWS(&df::itemdef_handlerst::shoes, df::job_type::MakeShoes, df::item_type::SHOES)) + return v; + if (auto v = SEARCH_ITEM_RAWS(&df::itemdef_handlerst::helms, df::job_type::MakeHelm, df::item_type::HELM)) + return v; + if (auto v = SEARCH_ITEM_RAWS(&df::itemdef_handlerst::pants, df::job_type::MakePants, df::item_type::PANTS)) + return v; + return std::nullopt; } -static bool setItemFromToken(string token, ClothingRequirement *requirement) { +static std::optional setItemFromToken(string token) { ItemTypeInfo itemInfo; if (!itemInfo.find(token)) - return false; + return std::nullopt; switch (itemInfo.type) { case item_type::ARMOR: - requirement->jobType = job_type::MakeArmor; - break; + return ClothingRequirement{.jobType = job_type::MakeArmor, .itemType = itemInfo.type, .item_subtype = itemInfo.subtype}; case item_type::GLOVES: - requirement->jobType = job_type::MakeGloves; - break; + return ClothingRequirement{.jobType = job_type::MakeGloves, .itemType = itemInfo.type, .item_subtype = itemInfo.subtype}; case item_type::SHOES: - requirement->jobType = job_type::MakeShoes; - break; + return ClothingRequirement{.jobType = job_type::MakeShoes, .itemType = itemInfo.type, .item_subtype = itemInfo.subtype}; case item_type::HELM: - requirement->jobType = job_type::MakeHelm; - break; + return ClothingRequirement{.jobType = job_type::MakeHelm, .itemType = itemInfo.type, .item_subtype = itemInfo.subtype}; case item_type::PANTS: - requirement->jobType = job_type::MakePants; - break; + return ClothingRequirement{.jobType = job_type::MakePants, .itemType = itemInfo.type, .item_subtype = itemInfo.subtype}; default: - return false; + return std::nullopt; } - requirement->itemType = itemInfo.type; - requirement->item_subtype = itemInfo.subtype; - return true; } -static bool setItem(string name, ClothingRequirement *requirement) { - return setItemFromName(name, requirement) || setItemFromToken(name, requirement); +static std::optional setItem(string name) +{ + if (auto v = setItemFromName(name)) return v; + return setItemFromToken(name); } -static bool armorFlagsMatch(BitArray *flags, df::job_material_category *category) { - if (flags->is_set(df::armor_general_flags::SOFT) && - (category->bits.cloth || category->bits.yarn || category->bits.silk) +static bool armorFlagsMatch(BitArray& flags, df::job_material_category& category) { + if (flags.is_set(df::armor_general_flags::SOFT) && + (category.bits.cloth || category.bits.yarn || category.bits.silk) ) return true; - else if (flags->is_set(df::armor_general_flags::BARRED) && category->bits.bone) + else if (flags.is_set(df::armor_general_flags::BARRED) && category.bits.bone) return true; - else if (flags->is_set(df::armor_general_flags::SCALED) && category->bits.shell) + else if (flags.is_set(df::armor_general_flags::SCALED) && category.bits.shell) return true; - return flags->is_set(df::armor_general_flags::LEATHER) && category->bits.leather; + return flags.is_set(df::armor_general_flags::LEATHER) && category.bits.leather; } -static bool validateMaterialCategory(ClothingRequirement *requirement) { - auto itemDef = Items::getSubtypeDef(requirement->itemType, requirement->item_subtype); - switch (requirement->itemType) +static bool validateMaterialCategory(ClothingRequirement& requirement) { + auto itemDef = Items::getSubtypeDef(requirement.itemType, requirement.item_subtype); + switch (requirement.itemType) { case item_type::ARMOR: if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_armorst, itemDef)) - return armorFlagsMatch(&armor->props.flags, &requirement->material_category); + return armorFlagsMatch(armor->props.flags, requirement.material_category); break; case item_type::GLOVES: if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_glovesst, itemDef)) - return armorFlagsMatch(&armor->props.flags, &requirement->material_category); + return armorFlagsMatch(armor->props.flags, requirement.material_category); break; case item_type::SHOES: if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_shoesst, itemDef)) - return armorFlagsMatch(&armor->props.flags, &requirement->material_category); + return armorFlagsMatch(armor->props.flags, requirement.material_category); break; case item_type::HELM: if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_helmst, itemDef)) - return armorFlagsMatch(&armor->props.flags, &requirement->material_category); + return armorFlagsMatch(armor->props.flags, requirement.material_category); break; case item_type::PANTS: if (STRICT_VIRTUAL_CAST_VAR(armor, df::itemdef_pantsst, itemDef)) - return armorFlagsMatch(&armor->props.flags, &requirement->material_category); + return armorFlagsMatch(armor->props.flags, requirement.material_category); break; default: break; @@ -410,7 +424,7 @@ static bool validateMaterialCategory(ClothingRequirement *requirement) { command_result autoclothing(color_ostream &out, vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -448,12 +462,15 @@ command_result autoclothing(color_ostream &out, vector ¶meters) } // Create a new requirement from the available parameters. - ClothingRequirement newRequirement; - if (!newRequirement.SetFromParameters(out, parameters)) + auto newRequirementOpt = ClothingRequirement::createFromParameters(out, parameters); + if (!newRequirementOpt) return CR_WRONG_USAGE; + + auto& newRequirement = *newRequirementOpt; + // All checks are passed. Now we either show or set the amount. bool settingSize = false; - bool matchedExisting = false; + if (parameters.size() > 2) { if (parameters[0] == "clear") { newRequirement.needed_per_citizen = 0; @@ -471,28 +488,30 @@ command_result autoclothing(color_ostream &out, vector ¶meters) } } - for (size_t i = clothingOrders.size(); i-- > 0;) { - if (!clothingOrders[i].matches(&newRequirement)) - continue; - matchedExisting = true; - if (settingSize) { - if (newRequirement.needed_per_citizen == 0) { - clothingOrders.erase(clothingOrders.begin() + i); - if (parameters[0] == "clear") - out << "Unset " << parameters[1] << " " << parameters[2] << endl; - else - out << "Unset " << parameters[0] << " " << parameters[1] << endl; + auto it = std::find(clothingOrders.begin(), clothingOrders.end(), newRequirement); + if (it != clothingOrders.end()) + { + if (settingSize) + { + if (newRequirement.needed_per_citizen == 0) + { + clothingOrders.erase(it); + if (parameters[0] == "clear") + out << "Unset " << parameters[1] << " " << parameters[2] << endl; + else + out << "Unset " << parameters[0] << " " << parameters[1] << endl; } - else { - clothingOrders[i] = newRequirement; + else + { + *it = newRequirement; out << "Set " << parameters[0] << " " << parameters[1] << " to " << parameters[2] << endl; } } else - out << parameters[0] << " " << parameters[1] << " is set to " << clothingOrders[i].needed_per_citizen << endl; - break; + out << parameters[0] << " " << parameters[1] << " is set to " << it->needed_per_citizen << endl; } - if (!matchedExisting) { + else + { if (settingSize) { if (newRequirement.needed_per_citizen == 0) out << parameters[0] << " " << parameters[1] << " already unset." << endl; @@ -527,7 +546,7 @@ static void find_needed_clothing_items() { { auto item = Items::findItemByID(ownedItem); if (!item) { - DEBUG(cycle).print("autoclothing: Invalid inventory item ID: %d\n", ownedItem); + DEBUG(cycle).print("autoclothing: Invalid inventory item ID: {}\n", ownedItem); continue; } @@ -613,6 +632,7 @@ static void add_clothing_orders() { newOrder->material_category = clothingOrder.material_category; newOrder->amount_left = amount; newOrder->amount_total = amount; + newOrder->frequency = df::workquota_frequency_type::OneTime; world->manager_orders.all.push_back(newOrder); } } @@ -683,7 +703,7 @@ static void generate_control(color_ostream &out) { { auto item = Items::findItemByID(itemId); if (!item) { - DEBUG(cycle, out).print("autoclothing: Invalid inventory item ID: %d\n", itemId); + DEBUG(cycle, out).print("autoclothing: Invalid inventory item ID: {}\n", itemId); continue; } else if (item->getWear() >= 1) @@ -786,7 +806,7 @@ static void generate_control(color_ostream &out) { } map availableGloves; - for (auto glove : world->items.other.HELM) { + for (auto glove : world->items.other.GLOVES) { if (!isAvailableItem(glove)) continue; availableGloves[glove->maker_race]++; @@ -797,7 +817,7 @@ static void generate_control(color_ostream &out) { } map availablePants; - for (auto pants : world->items.other.HELM) { + for (auto pants : world->items.other.PANTS) { if (!isAvailableItem(pants)) continue; availablePants[pants->maker_race]++; diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index fbf3ff8a82..4376a888d0 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -170,7 +170,7 @@ static command_result autodump_main(color_ostream &out, vector ¶mete } } else - out.print("Could not move item: %s\n", Items::getDescription(itm, 0, true).c_str()); + out.print("Could not move item: {}\n", Items::getDescription(itm, 0, true)); } } else { // Destroy @@ -184,7 +184,7 @@ static command_result autodump_main(color_ostream &out, vector ¶mete dumped_total++; } - out.print("Done. %d items %s.\n", dumped_total, destroy ? "marked for destruction" : "quickdumped"); + out.print("Done. {} items {}.\n", dumped_total, destroy ? "marked for destruction" : "quickdumped"); return CR_OK; } diff --git a/plugins/autofarm.cpp b/plugins/autofarm.cpp index 571cd13608..65a08123d9 100644 --- a/plugins/autofarm.cpp +++ b/plugins/autofarm.cpp @@ -22,6 +22,7 @@ #include "df/unit.h" #include "df/world.h" +#include #include using namespace DFHack; @@ -199,9 +200,9 @@ class AutoFarm { if (old_plant_id != new_plant_id) { farm->plant_id[season] = new_plant_id; - INFO(cycle, out).print("autofarm: changing farm #%d from %s to %s\n", farm->id, - get_plant_name(old_plant_id).c_str(), - get_plant_name(new_plant_id).c_str()); + INFO(cycle, out).print("autofarm: changing farm #{} from {} to {}\n", farm->id, + get_plant_name(old_plant_id), + get_plant_name(new_plant_id)); } } @@ -389,11 +390,11 @@ class AutoFarm { if (plant != std::end(allPlants)) { setThreshold((*plant)->index, val); - INFO(control, out).print("threshold of %d for plant %s in saved configuration loaded\n", val, id.c_str()); + INFO(control, out).print("threshold of {} for plant {} in saved configuration loaded\n", val, id); } else { - WARN(control, out).print("threshold for unknown plant %s in saved configuration ignored\n", id.c_str()); + WARN(control, out).print("threshold for unknown plant {} in saved configuration ignored\n", id); } } } @@ -453,7 +454,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream& out) DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -512,7 +513,7 @@ static command_result setThresholds(color_ostream& out, std::vector static command_result autofarm(color_ostream& out, std::vector& parameters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } diff --git a/plugins/autogems.cpp b/plugins/autogems.cpp index 593e64670a..249552a006 100644 --- a/plugins/autogems.cpp +++ b/plugins/autogems.cpp @@ -314,7 +314,7 @@ bool read_config(color_ostream &out) { } } catch (Json::Exception &e) { - out.printerr("autogems: failed to read autogems.json: %s\n", e.what()); + out.printerr("autogems: failed to read autogems.json: {}\n", e.what()); return false; } @@ -326,7 +326,7 @@ bool read_config(color_ostream &out) { blacklist.insert(mat_index(item.asInt())); } else { - out.printerr("autogems: illegal item at position %i in blacklist\n", i); + out.printerr("autogems: illegal item at position {} in blacklist\n", i); } } } @@ -355,7 +355,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { if (enable != enabled) { if (!INTERPOSE_HOOK(autogem_hook, feed).apply(enable) || !INTERPOSE_HOOK(autogem_hook, render).apply(enable)) { - out.printerr("Could not %s autogem hooks!\n", enable? "insert": "remove"); + out.printerr("Could not {} autogem hooks!\n", enable? "insert": "remove"); return CR_FAILURE; } diff --git a/plugins/autolabor/autolabor.cpp b/plugins/autolabor/autolabor.cpp index d755202de6..4fd73ce181 100644 --- a/plugins/autolabor/autolabor.cpp +++ b/plugins/autolabor/autolabor.cpp @@ -697,8 +697,8 @@ static void assign_labor(unit_labor::unit_labor labor, dwarfs[dwarf]->uniform.pickup_flags.bits.update = 1; } - TRACE(cycle, out).print("Dwarf % i \"%s\" assigned %s: value %i %s %s\n", - dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str(), values[dwarf], + TRACE(cycle, out).print("Dwarf {} \"{}\" assigned {}: value {} {} {}\n", + dwarf, dwarfs[dwarf]->name.first_name, ENUM_KEY_STR(unit_labor, labor), values[dwarf], dwarf_info[dwarf].trader ? "(trader)" : "", dwarf_info[dwarf].diplomacy ? "(diplomacy)" : ""); @@ -740,7 +740,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } - if (world->frame_counter - cycle_timestamp <= CYCLE_TICKS) + if (world->frame_counter - cycle_timestamp < CYCLE_TICKS) return CR_OK; cycle_timestamp = world->frame_counter; @@ -766,7 +766,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { df::building_tradedepotst* depot = (df::building_tradedepotst*) build; trader_requested = trader_requested || depot->trade_flags.bits.trader_requested; - TRACE(cycle,out).print(trader_requested + TRACE(cycle,out).print("{}", trader_requested ? "Trade depot found and trader requested, trader will be excluded from all labors.\n" : "Trade depot found but trader is not requested.\n" ); @@ -846,8 +846,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (p1 || p2) { dwarf_info[dwarf].diplomacy = true; - DEBUG(cycle, out).print("Dwarf %i \"%s\" has a meeting, will be cleared of all labors\n", - dwarf, dwarfs[dwarf]->name.first_name.c_str()); + DEBUG(cycle, out).print("Dwarf {} \"{}\" has a meeting, will be cleared of all labors\n", + dwarf, dwarfs[dwarf]->name.first_name); break; } } @@ -919,15 +919,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) dwarf_info[dwarf].state = dwarf_states[job]; else { - WARN(cycle, out).print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job); + WARN(cycle, out).print("Dwarf {} \"{}\" has unknown job {}\n", dwarf, dwarfs[dwarf]->name.first_name, job); dwarf_info[dwarf].state = OTHER; } } state_count[dwarf_info[dwarf].state]++; - TRACE(cycle, out).print("Dwarf %i \"%s\": penalty %i, state %s\n", - dwarf, dwarfs[dwarf]->name.first_name.c_str(), dwarf_info[dwarf].mastery_penalty, state_names[dwarf_info[dwarf].state]); + TRACE(cycle, out).print("Dwarf {} \"{}\": penalty {}, state {}\n", + dwarf, dwarfs[dwarf]->name.first_name, dwarf_info[dwarf].mastery_penalty, state_names[dwarf_info[dwarf].state]); } std::vector labors; @@ -1034,8 +1034,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY || dwarf_info[dwarf].state == EXCLUSIVE) labor_infos[labor].active_dwarfs++; - TRACE(cycle, out).print("Dwarf %i \"%s\" assigned %s: hauler\n", - dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str()); + TRACE(cycle, out).print("Dwarf {} \"{}\" assigned {}: hauler\n", + dwarf, dwarfs[dwarf]->name.first_name, ENUM_KEY_STR(unit_labor, labor)); } for (size_t i = num_haulers; i < hauler_ids.size(); i++) @@ -1076,7 +1076,7 @@ void print_labor (df::unit_labor labor, color_ostream &out) DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -1095,7 +1095,7 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) command_result autolabor (color_ostream &out, std::vector & parameters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -1137,7 +1137,7 @@ command_result autolabor (color_ostream &out, std::vector & parame if (labor == unit_labor::NONE) { - out.printerr("Could not find labor %s.\n", parameters[0].c_str()); + out.printerr("Could not find labor {}.\n", parameters[0]); return CR_WRONG_USAGE; } @@ -1171,7 +1171,7 @@ command_result autolabor (color_ostream &out, std::vector & parame if (maximum < minimum || maximum < 0 || minimum < 0) { - out.printerr("Syntax: autolabor [] [], %d > %d\n", maximum, minimum); + out.printerr("Syntax: autolabor [] [], {} > {}\n", maximum, minimum); return CR_WRONG_USAGE; } @@ -1235,7 +1235,7 @@ command_result autolabor (color_ostream &out, std::vector & parame { out.print("Automatically assigns labors to dwarves.\n" "Activate with 'enable autolabor', deactivate with 'disable autolabor'.\n" - "Current state: %d.\n", enable_autolabor); + "Current state: {}.\n", enable_autolabor); return CR_OK; } diff --git a/plugins/autolabor/laborstatemap.h b/plugins/autolabor/laborstatemap.h index a9472bcc2e..3757011d63 100644 --- a/plugins/autolabor/laborstatemap.h +++ b/plugins/autolabor/laborstatemap.h @@ -282,6 +282,12 @@ const dwarf_state dwarf_states[] = { dwarf_state::OTHER /* HeistItem */, dwarf_state::OTHER /* InterrogateSubject */, dwarf_state::OTHER /* AcceptHeistItem */, + dwarf_state::BUSY /* StoreSquadEquipmentItem */, + dwarf_state::BUSY /* MixDye */, + dwarf_state::BUSY /* DyeLeather */, + dwarf_state::BUSY /* ConstructBoltThrowerParts */, + dwarf_state::BUSY /* LoadBoltThrower */, + dwarf_state::BUSY /* FireBoltThrower */, }; #define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index f7f1bdd84b..cc45507b2b 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -63,17 +63,17 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -235,9 +235,9 @@ static bool assignUnitToZone(color_ostream &out, df::unit *unit, df::building_ci unit->general_refs.push_back(ref); zone->assigned_units.push_back(unit->id); - INFO(cycle,out).print("Unit %d (%s) assigned to nestbox zone %d (%s)\n", - unit->id, Units::getRaceName(unit).c_str(), - zone->id, zone->name.c_str()); + INFO(cycle,out).print("Unit {} ({}) assigned to nestbox zone {} ({})\n", + unit->id, Units::getRaceName(unit), + zone->id, zone->name); return true; } @@ -247,13 +247,13 @@ static bool assignUnitToZone(color_ostream &out, df::unit *unit, df::building_ci static size_t getFreeNestboxZones(color_ostream &out, vector &free_zones) { size_t assigned = 0; for (auto zone : world->buildings.other.ZONE_PEN) { - TRACE(cycle,out).print("scanning pasture %d (%s)\n", zone->id, zone->name.c_str()); + TRACE(cycle,out).print("scanning pasture {} ({})\n", zone->id, zone->name); if (!Buildings::isActive(zone)) { - TRACE(cycle,out).print("pasture %d is inactive\n", zone->id); + TRACE(cycle,out).print("pasture {} is inactive\n", zone->id); continue; } if (!isEmptyPasture(zone)) { - TRACE(cycle,out).print("pasture %d is not empty\n", zone->id); + TRACE(cycle,out).print("pasture {} is not empty\n", zone->id); continue; } @@ -262,23 +262,23 @@ static size_t getFreeNestboxZones(color_ostream &out, vectorx1, zone->y1, zone->z); auto bld = Buildings::findAtTile(pos); if (!bld || bld->getType() != df::building_type::NestBox) { - TRACE(cycle,out).print("pasture %d does not have nestbox in upper left corner\n", zone->id); + TRACE(cycle,out).print("pasture {} does not have nestbox in upper left corner\n", zone->id); continue; } - TRACE(cycle,out).print("found nestbox %d in pasture %d\n", bld->id, zone->id); + TRACE(cycle,out).print("found nestbox {} in pasture {}\n", bld->id, zone->id); df::building_nest_boxst *nestbox = virtual_cast(bld); if (!nestbox) { - TRACE(cycle,out).print("nestbox %d is somehow not a nestbox\n", bld->id); + TRACE(cycle,out).print("nestbox {} is somehow not a nestbox\n", bld->id); continue; } if (nestbox->claimed_by >= 0) { if (auto unit = df::unit::find(nestbox->claimed_by)) { - TRACE(cycle,out).print("nestbox %d is claimed by unit %d (%s)\n", bld->id, - nestbox->claimed_by, Units::getReadableName(unit).c_str()); + TRACE(cycle,out).print("nestbox {} is claimed by unit {} ({})\n", bld->id, + nestbox->claimed_by, Units::getReadableName(unit)); if (!isFreeEgglayer(unit)) { - DEBUG(cycle,out).print("cannot assign unit %d to nestbox %d: not a free egg layer\n", unit->id, bld->id); + DEBUG(cycle,out).print("cannot assign unit {} to nestbox {}: not a free egg layer\n", unit->id, bld->id); } else { // if the nestbox is claimed by a free egg layer, attempt to assign that unit to the zone if (assignUnitToZone(out, unit, zone)) @@ -327,7 +327,7 @@ static size_t assign_nestboxes(color_ostream &out) { DEBUG(cycle,out).print("Failed to assign unit to building.\n"); return assigned; } - DEBUG(cycle,out).print("assigned unit %d to zone %d\n", + DEBUG(cycle,out).print("assigned unit {} to zone {}\n", free_units[idx]->id, free_zones[idx]->id); ++assigned; } diff --git a/plugins/autoslab.cpp b/plugins/autoslab.cpp index f8f93ac747..e8c06d575d 100644 --- a/plugins/autoslab.cpp +++ b/plugins/autoslab.cpp @@ -53,7 +53,7 @@ static void do_cycle(color_ostream &out); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(control, out).print("initializing %s\n", plugin_name); + DEBUG(control, out).print("initializing {}\n", plugin_name); return CR_OK; } @@ -62,28 +62,28 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } if (enable != is_enabled) { is_enabled = enable; - DEBUG(control, out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled"); + DEBUG(control, out).print("{} from the API; persisting\n", is_enabled ? "enabled" : "disabled"); config.set_bool(CONFIG_IS_ENABLED, is_enabled); if (enable) do_cycle(out); } else { - DEBUG(control, out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); + DEBUG(control, out).print("{} from the API, but already {}; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); } return CR_OK; } DFhackCExport command_result plugin_shutdown(color_ostream &out) { - DEBUG(control, out).print("shutting down %s\n", plugin_name); + DEBUG(control, out).print("shutting down {}\n", plugin_name); return CR_OK; } @@ -104,7 +104,7 @@ DFhackCExport command_result plugin_load_site_data(color_ostream &out) // all the other state we can directly read/modify from the persistent // data structure. is_enabled = config.get_bool(CONFIG_IS_ENABLED); - DEBUG(control, out).print("loading persisted enabled state: %s\n", is_enabled ? "true" : "false"); + DEBUG(control, out).print("loading persisted enabled state: {}\n", is_enabled ? "true" : "false"); return CR_OK; } @@ -114,7 +114,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan { if (is_enabled) { - DEBUG(control, out).print("world unloaded; disabling %s\n", plugin_name); + DEBUG(control, out).print("world unloaded; disabling {}\n", plugin_name); is_enabled = false; } } @@ -141,6 +141,7 @@ static void createSlabJob(df::unit *unit) order->specdata.hist_figure_id = unit->hist_figure_id; order->amount_left = 1; order->amount_total = 1; + order->frequency = df::workquota_frequency_type::OneTime; world->manager_orders.all.push_back(order); } @@ -174,7 +175,7 @@ static void checkslabs(color_ostream &out) { createSlabJob(ghost); auto fullName = Units::getReadableName(ghost); - out.print("Added slab order for %s\n", DF2CONSOLE(fullName).c_str()); + out.print("Added slab order for {}\n", DF2CONSOLE(fullName)); } } } diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 26caf25816..3f046bc142 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -6,6 +6,7 @@ */ #include "Console.h" +#include "Core.h" #include "DataDefs.h" #include "DataFuncs.h" #include "DataIdentity.h" @@ -16,8 +17,10 @@ #include "TileTypes.h" #include "modules/Buildings.h" +#include "modules/Constructions.h" #include "modules/Filesystem.h" #include "modules/Gui.h" +#include "modules/Hotkey.h" #include "modules/Maps.h" #include "modules/World.h" @@ -33,6 +36,7 @@ #include "df/building_trapst.h" #include "df/building_water_wheelst.h" #include "df/building_workshopst.h" +#include "df/construction.h" #include "df/engraving.h" #include "df/entity_position.h" #include "df/tile_bitmask.h" @@ -56,8 +60,6 @@ using namespace DFHack; DFHACK_PLUGIN("blueprint"); REQUIRE_GLOBAL(world); -static const string BLUEPRINT_USER_DIR = "dfhack-config/blueprints/"; - namespace DFHack { DBG_DECLARE(blueprint,log); } @@ -610,6 +612,7 @@ static const char * get_construction_str(df::building *b) { case construction_type::TrackRampNEW: return "trackrampNEW"; case construction_type::TrackRampSEW: return "trackrampSEW"; case construction_type::TrackRampNSEW: return "trackrampNSEW"; + case construction_type::ReinforcedWall: return "CW"; case construction_type::NONE: default: return "~"; @@ -632,6 +635,13 @@ static const char * get_constructed_track_str(df::tiletype *tt, return cache(str); } +static const char * get_constructed_wall_str(df::coord pos) { + df::construction *c = Constructions::findAtTile(pos); + if (!c || !(c->flags.bits.reinforced)) + return "Cw"; + return "CW"; +} + static const char * get_constructed_floor_str(df::tiletype *tt) { if (tileSpecial(*tt) != df::tiletype_special::TRACK) return "Cf"; @@ -653,7 +663,7 @@ static const char * get_tile_construct(color_ostream &out, const df::coord &pos, return NULL; switch (tileShape(*tt)) { - case tiletype_shape::WALL: return "Cw"; + case tiletype_shape::WALL: return get_constructed_wall_str(pos); case tiletype_shape::FLOOR: return get_constructed_floor_str(tt); case tiletype_shape::RAMP: return get_constructed_ramp_str(tt); case tiletype_shape::FORTIFICATION: return "CF"; @@ -732,7 +742,17 @@ static const char * get_bridge_str(df::building *b) { static const char * get_siege_str(df::building *b) { df::building_siegeenginest *se = virtual_cast(b); - return !se || se->type == df::siegeengine_type::Catapult ? "ic" : "ib"; + if (!se) + return "ic"; + + switch(se->type) { + case df::siegeengine_type::Catapult: return "ic"; + case df::siegeengine_type::Ballista: return "ib"; + case df::siegeengine_type::BoltThrower: return "it"; + default: + return "ic"; + } + } static const char * get_workshop_str(df::building *b) { @@ -1349,14 +1369,14 @@ static const char * get_tile_zone(color_ostream &out, const df::coord &pos, cons static bool create_output_dir(color_ostream &out, const blueprint_options &opts) { - string basename = BLUEPRINT_USER_DIR + opts.name; - size_t last_slash = basename.find_last_of("/"); - string parent_path = basename.substr(0, last_slash); + std::filesystem::path BLUEPRINT_USER_DIR = Core::getInstance().getConfigPath() / "blueprints"; + std::filesystem::path basename = BLUEPRINT_USER_DIR / opts.name; + std::filesystem::path parent_path = basename.parent_path(); // create output directory if it doesn't already exist if (!Filesystem::mkdir_recursive(parent_path)) { - out.printerr("could not create output directory: '%s'\n", - parent_path.c_str()); + out.printerr("could not create output directory: '{}'\n", + parent_path); return false; } return true; @@ -1646,9 +1666,9 @@ static command_result do_blueprint(color_ostream &out, command << " " << parameters[i]; } string command_str = command.str(); - out.print("launching %s\n", command_str.c_str()); + out.print("launching {}\n", command_str); - Core::getInstance().setHotkeyCmd(command_str); + Core::getInstance().getHotkeyManager()->setHotkeyCommand(command_str); return CR_OK; } @@ -1674,7 +1694,7 @@ static command_result do_blueprint(color_ostream &out, } } if (!Maps::isValidTilePos(start)) { - out.printerr("Invalid start position: %d,%d,%d\n", + out.printerr("Invalid start position: {},{},{},\n", start.x, start.y, start.z); return CR_FAILURE; } @@ -1737,7 +1757,7 @@ command_result blueprint(color_ostream &out, vector ¶meters) { else { out.print("Generated blueprint file(s):\n"); for (string &fname : files) - out.print(" %s\n", fname.c_str()); + out.print(" {}\n", fname); } } return cr; diff --git a/plugins/buildingplan/buildingplan.cpp b/plugins/buildingplan/buildingplan.cpp index 64ddf89e34..6f5a13a8c7 100644 --- a/plugins/buildingplan/buildingplan.cpp +++ b/plugins/buildingplan/buildingplan.cpp @@ -64,7 +64,7 @@ static Tasks tasks; // planned_buildings, then it has either been built or desroyed. therefore there is // no chance of duplicate tasks getting added to the tasks queues. void PlannedBuilding::remove(color_ostream &out) { - DEBUG(control,out).print("removing persistent data for building %d\n", id); + DEBUG(control,out).print("removing persistent data for building {}\n", id); World::DeletePersistentData(bld_config); planned_buildings.erase(id); } @@ -97,8 +97,8 @@ static const vector & get_job_items(color_ostream &out, Bu std::make_tuple(std::get<0>(key), std::get<1>(key), std::get<2>(key), index+1), 1, [&](lua_State *L) { df::job_item *jitem = Lua::GetDFObject(L, -1); - DEBUG(control,out).print("retrieving job_item for (%d, %d, %d) index=%d: 0x%p\n", - std::get<0>(key), std::get<1>(key), std::get<2>(key), index, jitem); + DEBUG(control,out).print("retrieving job_item for {} index={}: {}\n", + key, index, static_cast(jitem)); if (!jitem) failed = true; else @@ -125,35 +125,35 @@ static void cache_matched(int16_t type, int32_t index) { MaterialInfo mi; mi.decode(type, index); if (mi.matches(stone_cat)) { - DEBUG(control).print("cached stone material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + DEBUG(control).print("cached stone material: {} ({}, {})\n", mi.toString(), type, index); mat_cache.emplace(mi.toString(), std::make_pair(mi, "stone")); } else if (mi.matches(wood_cat)) { - DEBUG(control).print("cached wood material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + DEBUG(control).print("cached wood material: {} ({}, {})\n", mi.toString(), type, index); mat_cache.emplace(mi.toString(), std::make_pair(mi, "wood")); } else if (mi.matches(metal_cat)) { - DEBUG(control).print("cached metal material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + DEBUG(control).print("cached metal material: {} ({}, {})\n", mi.toString(), type, index); mat_cache.emplace(mi.toString(), std::make_pair(mi, "metal")); } else if (mi.matches(glass_cat)) { - DEBUG(control).print("cached glass material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + DEBUG(control).print("cached glass material: {} ({}, {})\n", mi.toString(), type, index); mat_cache.emplace(mi.toString(), std::make_pair(mi, "glass")); } else if (mi.matches(gem_cat)) { - DEBUG(control).print("cached gem material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + DEBUG(control).print("cached gem material: {} ({}, {})\n", mi.toString(), type, index); mat_cache.emplace(mi.toString(), std::make_pair(mi, "gem")); } else if (mi.matches(clay_cat)) { - DEBUG(control).print("cached clay material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + DEBUG(control).print("cached clay material: {} ({}, {})\n", mi.toString(), type, index); mat_cache.emplace(mi.toString(), std::make_pair(mi, "clay")); } else if (mi.matches(cloth_cat)) { - DEBUG(control).print("cached cloth material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + DEBUG(control).print("cached cloth material: {} ({}, {})\n", mi.toString(), type, index); mat_cache.emplace(mi.toString(), std::make_pair(mi, "cloth")); } else if (mi.matches(silk_cat)) { - DEBUG(control).print("cached silk material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + DEBUG(control).print("cached silk material: {} ({}, {})\n", mi.toString(), type, index); mat_cache.emplace(mi.toString(), std::make_pair(mi, "silk")); } else if (mi.matches(yarn_cat)) { - DEBUG(control).print("cached yarn material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + DEBUG(control).print("cached yarn material: {} ({}, {})\n", mi.toString(), type, index); mat_cache.emplace(mi.toString(), std::make_pair(mi, "yarn")); } else - TRACE(control).print("not matched: %s\n", mi.toString().c_str()); + TRACE(control).print("not matched: {}\n", mi.toString()); } static void load_organic_material_cache(df::organic_mat_category cat) { @@ -201,7 +201,7 @@ void buildingplan_cycle(color_ostream &out, Tasks &tasks, static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb, bool unsuspend_on_finalize); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(control,out).print("initializing %s\n", plugin_name); + DEBUG(control,out).print("initializing {}\n", plugin_name); // provide a configuration interface for the plugin commands.push_back(PluginCommand( @@ -214,12 +214,12 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector getType(), get_subtype(bld), bld->getCustomType()); if (pb.item_filters.size() != get_item_filters(out, key).getItemFilters().size()) { - WARN(control).print("loaded state for building %d doesn't match world\n", pb.id); + WARN(control).print("loaded state for building {} doesn't match world\n", pb.id); pb.remove(out); continue; } @@ -354,7 +354,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { static command_result do_command(color_ostream &out, vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot configure %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot configure {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -428,8 +428,8 @@ vector getVectorIds(color_ostream &out, const df::job_it // if the filter already has the vector_id set to something specific, use it if (job_item->vector_id > df::job_item_vector_id::IN_PLAY) { - DEBUG(control,out).print("using vector_id from job_item: %s\n", - ENUM_KEY_STR(job_item_vector_id, job_item->vector_id).c_str()); + DEBUG(control,out).print("using vector_id from job_item: {}\n", + ENUM_KEY_STR(job_item_vector_id, job_item->vector_id)); ret.push_back(job_item->vector_id); return ret; } @@ -460,7 +460,7 @@ static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb, bo return false; if (bld->jobs.size() != 1) { - DEBUG(control,out).print("unexpected number of jobs: want 1, got %zu\n", bld->jobs.size()); + DEBUG(control,out).print("unexpected number of jobs: want 1, got {}\n", bld->jobs.size()); return false; } @@ -489,10 +489,10 @@ static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb, bo for (auto vector_id : pb.vector_ids[job_item_idx]) { for (int item_num = 0; item_num < job_item->quantity; ++item_num) { tasks[vector_id][bucket].emplace_back(id, rev_jitem_index); - DEBUG(control,out).print("added task: %s/%s/%d,%d; " - "%zu vector(s), %zu filter bucket(s), %zu task(s) in bucket\n", - ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), - bucket.c_str(), id, rev_jitem_index, tasks.size(), + DEBUG(control,out).print("added task: {}/{}/{}, {}; " + "{} vector(s), {} filter bucket(s), {} task(s) in bucket\n", + ENUM_KEY_STR(job_item_vector_id, vector_id), + bucket, id, rev_jitem_index, tasks.size(), tasks[vector_id].size(), tasks[vector_id][bucket].size()); } } @@ -520,16 +520,16 @@ static string get_desc_string(color_ostream &out, df::job_item *jitem, static void printStatus(color_ostream &out) { DEBUG(control,out).print("entering buildingplan_printStatus\n"); - out.print("buildingplan is %s\n\n", is_enabled ? "enabled" : "disabled"); + out.print("buildingplan is {}\n\n", is_enabled ? "enabled" : "disabled"); out.print("Current settings:\n"); - out.print(" use blocks: %s\n", config.get_bool(CONFIG_BLOCKS) ? "yes" : "no"); - out.print(" use boulders: %s\n", config.get_bool(CONFIG_BOULDERS) ? "yes" : "no"); - out.print(" use logs: %s\n", config.get_bool(CONFIG_LOGS) ? "yes" : "no"); - out.print(" use bars: %s\n", config.get_bool(CONFIG_BARS) ? "yes" : "no"); - out.print(" plan constructions on tiles with existing constructed floors/ramps when using box select: %s\n", + out.print(" use blocks: {}\n", config.get_bool(CONFIG_BLOCKS) ? "yes" : "no"); + out.print(" use boulders: {}\n", config.get_bool(CONFIG_BOULDERS) ? "yes" : "no"); + out.print(" use logs: {}\n", config.get_bool(CONFIG_LOGS) ? "yes" : "no"); + out.print(" use bars: {}\n", config.get_bool(CONFIG_BARS) ? "yes" : "no"); + out.print(" plan constructions on tiles with existing constructed floors/ramps when using box select: {}\n", config.get_bool(CONFIG_RECONSTRUCT) ? "yes" : "no"); auto burrow = df::burrow::find(config.get_int(CONFIG_BURROW)); - out.print(" ignore building materials in burrow: %s\n", burrow ? burrow->name.c_str() : "none"); + out.print(" ignore building materials in burrow: {}\n", burrow ? burrow->name.c_str() : "none"); out.print("\n"); size_t bld_count = 0; @@ -560,10 +560,10 @@ static void printStatus(color_ostream &out) { } if (bld_count) { - out.print("Waiting for %d item(s) to be produced for %zd building(s):\n", + out.print("Waiting for {} item(s) to be produced for {} building(s):\n", total, bld_count); for (auto &count : counts) - out.print(" %3d %s%s\n", count.second, count.first.c_str(), count.second == 1 ? "" : "s"); + out.print(" {:3} {}{}\n", count.second, count.first, count.second == 1 ? "" : "s"); } else { out.print("Currently no planned buildings\n"); } @@ -571,7 +571,7 @@ static void printStatus(color_ostream &out) { } static bool setSetting(color_ostream &out, string name, bool value) { - DEBUG(control,out).print("entering setSetting (%s -> %s)\n", name.c_str(), value ? "true" : "false"); + DEBUG(control,out).print("entering setSetting ({} -> {})\n", name, value ? "true" : "false"); if (name == "blocks") config.set_bool(CONFIG_BLOCKS, value); else if (name == "boulders") @@ -583,7 +583,7 @@ static bool setSetting(color_ostream &out, string name, bool value) { else if (name == "reconstruct") config.set_bool(CONFIG_RECONSTRUCT, value); else { - out.printerr("unrecognized setting: '%s'\n", name.c_str()); + out.printerr("unrecognized setting: '{}'\n", name); return false; } @@ -654,8 +654,8 @@ static int scanAvailableItems(color_ostream &out, df::building_type type, int16_ vector *item_ids = NULL, map *counts = NULL) { DEBUG(control,out).print( - "entering scanAvailableItems building_type=%d subtype=%d custom=%d index=%d\n", - type, subtype, custom, index); + "entering scanAvailableItems building_type={} subtype={} custom={} index={}\n", + ENUM_AS_STR(type), subtype, custom, index); BuildingTypeKey key(type, subtype, custom); HeatSafety heat = heat_override ? *heat_override : get_heat_safety_filter(key); auto &job_items = get_job_items(out, key); @@ -700,7 +700,7 @@ static int scanAvailableItems(color_ostream &out, df::building_type type, int16_ } } - DEBUG(control,out).print("found matches %d\n", count); + DEBUG(control,out).print("found matches {}\n", count); return count; } @@ -713,8 +713,8 @@ static int getAvailableItems(lua_State *L) { int32_t custom = luaL_checkint(L, 3); int index = luaL_checkint(L, 4); DEBUG(control,*out).print( - "entering getAvailableItems building_type=%d subtype=%d custom=%d index=%d\n", - type, subtype, custom, index); + "entering getAvailableItems building_type={} subtype={} custom={} index={}\n", + ENUM_AS_STR(type), subtype, custom, index); vector item_ids; scanAvailableItems(*out, type, subtype, custom, index, true, false, NULL, &item_ids); Lua::PushVector(L, item_ids); @@ -731,8 +731,8 @@ static int getAvailableItemsByHeat(lua_State *L) { int index = luaL_checkint(L, 4); HeatSafety heat = (HeatSafety)luaL_checkint(L, 5); DEBUG(control,*out).print( - "entering getAvailableItemsByHeat building_type=%d subtype=%d custom=%d index=%d\n", - type, subtype, custom, index); + "entering getAvailableItemsByHeat building_type={} subtype={} custom={} index={} heat={}\n", + ENUM_AS_STR(type), subtype, custom, index, static_cast(heat)); vector item_ids; scanAvailableItems(*out, type, subtype, custom, index, true, true, &heat, &item_ids); Lua::PushVector(L, item_ids); @@ -756,8 +756,8 @@ static int getGlobalSettings(lua_State *L) { static int countAvailableItems(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int index) { DEBUG(control,out).print( - "entering countAvailableItems building_type=%d subtype=%d custom=%d index=%d\n", - type, subtype, custom, index); + "entering countAvailableItems building_type={} subtype={} custom={} index={}\n", + ENUM_AS_STR(type), subtype, custom, index); int count = scanAvailableItems(out, type, subtype, custom, index, false, false); if (count) return count; @@ -816,8 +816,8 @@ static int setMaterialMaskFilter(lua_State *L) { int32_t custom = luaL_checkint(L, 3); int index = luaL_checkint(L, 4); DEBUG(control,*out).print( - "entering setMaterialMaskFilter building_type=%d subtype=%d custom=%d index=%d\n", - type, subtype, custom, index); + "entering setMaterialMaskFilter building_type={} subtype={} custom={} index={}\n", + ENUM_AS_STR(type), subtype, custom, index); BuildingTypeKey key(type, subtype, custom); auto &filters = get_item_filters(*out, key).getItemFilters(); if (index < 0 || filters.size() <= (size_t)index) @@ -846,8 +846,8 @@ static int setMaterialMaskFilter(lua_State *L) { mask |= yarn_cat.whole; } DEBUG(control,*out).print( - "setting material mask filter for building_type=%d subtype=%d custom=%d index=%d to %x\n", - type, subtype, custom, index, mask); + "setting material mask filter for building_type={} subtype={} custom={} index={} to {:x}\n", + ENUM_AS_STR(type), subtype, custom, index, mask); ItemFilter filter = filters[index]; filter.setMaterialMask(mask); set new_mats; @@ -875,8 +875,8 @@ static int getMaterialMaskFilter(lua_State *L) { int32_t custom = luaL_checkint(L, 3); int index = luaL_checkint(L, 4); DEBUG(control,*out).print( - "entering getMaterialFilter building_type=%d subtype=%d custom=%d index=%d\n", - type, subtype, custom, index); + "entering getMaterialFilter building_type={} subtype={} custom={} index={}\n", + ENUM_AS_STR(type), subtype, custom, index); BuildingTypeKey key(type, subtype, custom); auto &filters = get_item_filters(*out, key); if (index < 0 || filters.getItemFilters().size() <= (size_t)index) @@ -906,8 +906,8 @@ static int setMaterialFilter(lua_State *L) { int32_t custom = luaL_checkint(L, 3); int index = luaL_checkint(L, 4); DEBUG(control,*out).print( - "entering setMaterialFilter building_type=%d subtype=%d custom=%d index=%d\n", - type, subtype, custom, index); + "entering setMaterialFilter building_type={} subtype={} custom={} index={}\n", + ENUM_AS_STR(type), subtype, custom, index); BuildingTypeKey key(type, subtype, custom); auto &filters = get_item_filters(*out, key).getItemFilters(); if (index < 0 || filters.size() <= (size_t)index) @@ -920,8 +920,8 @@ static int setMaterialFilter(lua_State *L) { mats.emplace(mat_cache.at(mat).first); } DEBUG(control,*out).print( - "setting material filter for building_type=%d subtype=%d custom=%d index=%d to %zd materials\n", - type, subtype, custom, index, mats.size()); + "setting material filter for building_type={} subtype={} custom={} index={} to {} materials\n", + ENUM_AS_STR(type), subtype, custom, index, mats.size()); ItemFilter filter = filters[index]; filter.setMaterials(mats); // ensure relevant masks are explicitly enabled @@ -963,8 +963,8 @@ static int getMaterialFilter(lua_State *L) { int32_t custom = luaL_checkint(L, 3); int index = luaL_checkint(L, 4); DEBUG(control,*out).print( - "entering getMaterialFilter building_type=%d subtype=%d custom=%d index=%d\n", - type, subtype, custom, index); + "entering getMaterialFilter building_type={} subtype={} custom={} index={}\n", + ENUM_AS_STR(type), subtype, custom, index); BuildingTypeKey key(type, subtype, custom); auto &filters = get_item_filters(*out, key).getItemFilters(); if (index < 0 || filters.size() <= (size_t)index) @@ -1004,8 +1004,8 @@ static int getMaterialFilter(lua_State *L) { static void setChooseItems(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int choose) { DEBUG(control,out).print( - "entering setChooseItems building_type=%d subtype=%d custom=%d choose=%d\n", - type, subtype, custom, choose); + "entering setChooseItems building_type={} subtype={} custom={} choose={}\n", + ENUM_AS_STR(type), subtype, custom, choose); BuildingTypeKey key(type, subtype, custom); auto &filters = get_item_filters(out, key); filters.setChooseItems(choose); @@ -1020,8 +1020,8 @@ static int getChooseItems(lua_State *L) { int16_t subtype = luaL_checkint(L, 2); int32_t custom = luaL_checkint(L, 3); DEBUG(control,*out).print( - "entering getChooseItems building_type=%d subtype=%d custom=%d\n", - type, subtype, custom); + "entering getChooseItems building_type={} subtype={} custom={}\n", + ENUM_AS_STR(type), subtype, custom); BuildingTypeKey key(type, subtype, custom); Lua::Push(L, get_item_filters(*out, key).getChooseItems()); return 1; @@ -1045,8 +1045,8 @@ static int getHeatSafetyFilter(lua_State *L) { int16_t subtype = luaL_checkint(L, 2); int32_t custom = luaL_checkint(L, 3); DEBUG(control,*out).print( - "entering getHeatSafetyFilter building_type=%d subtype=%d custom=%d\n", - type, subtype, custom); + "entering getHeatSafetyFilter building_type={} subtype={} custom={}\n", + ENUM_AS_STR(type), subtype, custom); BuildingTypeKey key(type, subtype, custom); HeatSafety heat = get_heat_safety_filter(key); Lua::Push(L, heat); @@ -1069,8 +1069,8 @@ static int getSpecials(lua_State *L) { int16_t subtype = luaL_checkint(L, 2); int32_t custom = luaL_checkint(L, 3); DEBUG(control,*out).print( - "entering getSpecials building_type=%d subtype=%d custom=%d\n", - type, subtype, custom); + "entering getSpecials building_type={} subtype={} custom={}\n", + ENUM_AS_STR(type), subtype, custom); BuildingTypeKey key(type, subtype, custom); Lua::Push(L, get_item_filters(*out, key).getSpecials()); return 1; @@ -1100,8 +1100,8 @@ static int getQualityFilter(lua_State *L) { int32_t custom = luaL_checkint(L, 3); int index = luaL_checkint(L, 4); DEBUG(control,*out).print( - "entering getQualityFilter building_type=%d subtype=%d custom=%d index=%d\n", - type, subtype, custom, index); + "entering getQualityFilter building_type={} subtype={} custom={} index={}\n", + ENUM_AS_STR(type), subtype, custom, index); BuildingTypeKey key(type, subtype, custom); auto &filters = get_item_filters(*out, key).getItemFilters(); if (index < 0 || filters.size() <= (size_t)index) diff --git a/plugins/buildingplan/buildingplan_cycle.cpp b/plugins/buildingplan/buildingplan_cycle.cpp index 1a97a801de..7e8187dfce 100644 --- a/plugins/buildingplan/buildingplan_cycle.cpp +++ b/plugins/buildingplan/buildingplan_cycle.cpp @@ -77,7 +77,7 @@ static bool isAccessible(color_ostream& out, df::item* item) { df::coord item_pos = Items::getPosition(item); uint16_t walkability_group = Maps::getWalkableGroup(item_pos); bool is_walkable = accessible_walkability_groups.contains(walkability_group); - TRACE(cycle, out).print("item %d in walkability_group %u at (%d,%d,%d) is %saccessible from job site\n", + TRACE(cycle, out).print("item {} in walkability_group {} at ({},{},{}) is {}accessible from job site\n", item->id, walkability_group, item_pos.x, item_pos.y, item_pos.z, is_walkable ? "(probably) " : "not "); return is_walkable; } @@ -192,7 +192,7 @@ bool isJobReady(color_ostream &out, const std::vector &jitems) { int needed_items = 0; for (auto job_item : jitems) { needed_items += job_item->quantity; } if (needed_items) { - DEBUG(cycle,out).print("building needs %d more item(s)\n", needed_items); + DEBUG(cycle,out).print("building needs {} more item(s)\n", needed_items); return false; } return true; @@ -208,7 +208,7 @@ static bool job_item_idx_lt(df::job_item_ref *a, df::job_item_ref *b) { // remove them to keep the "finalize with buildingplan active" path as similar // as possible to the "finalize with buildingplan disabled" path. void finalizeBuilding(color_ostream &out, df::building *bld, bool unsuspend_on_finalize) { - DEBUG(cycle,out).print("finalizing building %d\n", bld->id); + DEBUG(cycle,out).print("finalizing building {}\n", bld->id); auto job = bld->jobs[0]; // sort the items so they get added to the structure in the correct order @@ -255,7 +255,7 @@ static df::building * popInvalidTasks(color_ostream &out, Bucket &task_queue, if (bld && bld->jobs[0]->job_items.elements[task.second]->quantity) return bld; } - DEBUG(cycle,out).print("discarding invalid task: bld=%d, job_item_idx=%d\n", id, task.second); + DEBUG(cycle,out).print("discarding invalid task: bld={}, job_item_idx={}\n", id, task.second); task_queue.pop_front(); } return NULL; @@ -273,9 +273,9 @@ static void doVector(color_ostream &out, df::job_item_vector_id vector_id, auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id); const auto item_vector = df::global::world->items.other[other_id]; - DEBUG(cycle,out).print("matching %zu item(s) in vector %s against %zu filter bucket(s)\n", + DEBUG(cycle,out).print("matching {} item(s) in vector {} against {} filter bucket(s)\n", item_vector.size(), - ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + ENUM_KEY_STR(job_item_vector_id, vector_id), buckets.size()); // items we might want to attach (and their positions) @@ -287,12 +287,12 @@ static void doVector(color_ostream &out, df::job_item_vector_id vector_id, std::vector> matching; size_t num_matching = 0; - DEBUG(cycle,out).print("%zu items available for assignment\n", available.size()); + DEBUG(cycle,out).print("{} items available for assignment\n", available.size()); for (auto bucket_it = buckets.begin(); bucket_it != buckets.end(); ) { - TRACE(cycle,out).print("scanning bucket: %s/%s\n", - ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), bucket_it->first.c_str()); + TRACE(cycle,out).print("scanning bucket: {}/{}\n", + ENUM_KEY_STR(job_item_vector_id, vector_id), bucket_it->first); auto & task_queue = bucket_it->second; bool first_task = true; @@ -322,7 +322,7 @@ static void doVector(color_ostream &out, df::job_item_vector_id vector_id, num_matching = matching.size(); first_task = false; - TRACE(cycle,out).print("first task in bucket: found %zu matching items\n", + TRACE(cycle,out).print("first task in bucket: found {} matching items\n", num_matching); } // every task: find and attach closest matching item (if any) @@ -342,15 +342,15 @@ static void doVector(color_ostream &out, df::job_item_vector_id vector_id, material.decode(item); ItemTypeInfo item_type; item_type.decode(item); - DEBUG(cycle,out).print("attached %s %s (distance %d) to filter %d for %s(%d): %s/%s\n", - material.toString().c_str(), - item_type.toString().c_str(), + DEBUG(cycle,out).print("attached {} {} (distance {}) to filter {} for {}({}): {}/{}\n", + material.toString(), + item_type.toString(), distance(closest->first, jpos), filter_idx, - ENUM_KEY_STR(building_type, bld->getType()).c_str(), + ENUM_KEY_STR(building_type, bld->getType()), id, - ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), - bucket_it->first.c_str()); + ENUM_KEY_STR(job_item_vector_id, vector_id), + bucket_it->first); // clean up fulfilled task task_queue.pop_front(); // keep quantity aligned with the actual number of remaining @@ -373,10 +373,10 @@ static void doVector(color_ostream &out, df::job_item_vector_id vector_id, if (task_queue.empty()) { DEBUG(cycle,out).print( - "removing empty item bucket: %s/%s; %zu left\n", - ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), - bucket_it->first.c_str(), - buckets.size() - 1); + "removing empty item bucket: {}/{}; {} left\n", + ENUM_KEY_STR(job_item_vector_id, vector_id), + bucket_it->first, + buckets.size() - 1); bucket_it = buckets.erase(bucket_it); } else { ++bucket_it; @@ -420,8 +420,8 @@ void buildingplan_cycle(color_ostream &out, Tasks &tasks, auto & buckets = it->second; doVector(out, vector_id, buckets, planned_buildings, unsuspend_on_finalize); if (buckets.empty()) { - DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n", - ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + DEBUG(cycle,out).print("removing empty vector: {}; {} vector(s) left\n", + ENUM_KEY_STR(job_item_vector_id, vector_id), tasks.size() - 1); it = tasks.erase(it); } @@ -434,12 +434,12 @@ void buildingplan_cycle(color_ostream &out, Tasks &tasks, auto & buckets = tasks[vector_id]; doVector(out, vector_id, buckets, planned_buildings, unsuspend_on_finalize); if (buckets.empty()) { - DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n", - ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + DEBUG(cycle,out).print("removing empty vector: {}; {} vector(s) left\n", + ENUM_KEY_STR(job_item_vector_id, vector_id), tasks.size() - 1); tasks.erase(vector_id); } } - DEBUG(cycle,out).print("cycle done; %zu registered building(s) left\n", + DEBUG(cycle,out).print("cycle done; {} registered building(s) left\n", planned_buildings.size()); } diff --git a/plugins/buildingplan/buildingtypekey.cpp b/plugins/buildingplan/buildingtypekey.cpp index 710878d643..e8136b8896 100644 --- a/plugins/buildingplan/buildingtypekey.cpp +++ b/plugins/buildingplan/buildingtypekey.cpp @@ -21,7 +21,7 @@ static BuildingTypeKey deserialize(color_ostream &out, const std::string &serial vector key_parts; split_string(&key_parts, serialized, ","); if (key_parts.size() != 3) { - WARN(control,out).print("invalid key_str: '%s'\n", serialized.c_str()); + WARN(control,out).print("invalid key_str: '{}'\n", serialized); return BuildingTypeKey(df::building_type::NONE, -1, -1); } return BuildingTypeKey((df::building_type)string_to_int(key_parts[0]), diff --git a/plugins/buildingplan/buildingtypekey.h b/plugins/buildingplan/buildingtypekey.h index 81bb043c55..5b21b6cc2c 100644 --- a/plugins/buildingplan/buildingtypekey.h +++ b/plugins/buildingplan/buildingtypekey.h @@ -17,6 +17,17 @@ struct BuildingTypeKey : public std::tuple std::string serialize() const; }; +template <> +struct fmt::formatter : fmt::formatter +{ + template + auto format(const BuildingTypeKey& key, FormatContext& ctx) const + { + return fmt::formatter::format( + fmt::format("({}, {}, {})", ENUM_AS_STR(std::get<0>(key)), std::get<1>(key), std::get<2>(key)), ctx); + } +}; + struct BuildingTypeKeyHash { std::size_t operator() (const BuildingTypeKey & key) const; }; diff --git a/plugins/buildingplan/defaultitemfilters.cpp b/plugins/buildingplan/defaultitemfilters.cpp index eaa11335d0..0e1cab8548 100644 --- a/plugins/buildingplan/defaultitemfilters.cpp +++ b/plugins/buildingplan/defaultitemfilters.cpp @@ -40,8 +40,8 @@ static string serialize(const std::vector &item_filters, const std:: DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey key, const std::vector &jitems) : key(key), choose_items(ItemSelectionChoice::ITEM_SELECTION_CHOICE_FILTER) { - DEBUG(control,out).print("creating persistent data for filter key %d,%d,%d\n", - std::get<0>(key), std::get<1>(key), std::get<2>(key)); + DEBUG(control,out).print("creating persistent data for filter key {},{},{}\n", + ENUM_AS_STR(std::get<0>(key)), std::get<1>(key), std::get<2>(key)); filter_config = World::AddPersistentSiteData(FILTER_CONFIG_KEY); filter_config.set_int(FILTER_CONFIG_TYPE, std::get<0>(key)); filter_config.set_int(FILTER_CONFIG_SUBTYPE, std::get<1>(key)); @@ -61,16 +61,16 @@ DefaultItemFilters::DefaultItemFilters(color_ostream &out, PersistentDataItem &f choose_items > ItemSelectionChoice::ITEM_SELECTION_CHOICE_AUTOMATERIAL) choose_items = ItemSelectionChoice::ITEM_SELECTION_CHOICE_FILTER; auto &serialized = filter_config.get_str(); - DEBUG(control,out).print("deserializing default item filters for key %d,%d,%d: %s\n", - std::get<0>(key), std::get<1>(key), std::get<2>(key), serialized.c_str()); + DEBUG(control,out).print("deserializing default item filters for key {},{},{}: {}\n", + ENUM_AS_STR(std::get<0>(key)), std::get<1>(key), std::get<2>(key), serialized); if (!jitems.size()) return; std::vector elems; split_string(&elems, serialized, "|"); std::vector filters = deserialize_item_filters(out, elems[0]); if (filters.size() != jitems.size()) { - WARN(control,out).print("ignoring invalid filters_str for key %d,%d,%d: '%s'\n", - std::get<0>(key), std::get<1>(key), std::get<2>(key), serialized.c_str()); + WARN(control,out).print("ignoring invalid filters_str for key {},{},{}: '{}'\n", + ENUM_AS_STR(std::get<0>(key)), std::get<1>(key), std::get<2>(key), serialized); item_filters.resize(jitems.size()); } else item_filters = filters; @@ -99,13 +99,13 @@ void DefaultItemFilters::setSpecial(const std::string &special, bool val) { void DefaultItemFilters::setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index) { if (index < 0 || item_filters.size() <= (size_t)index) { - WARN(control,out).print("invalid index for filter key %d,%d,%d: %d\n", - std::get<0>(key), std::get<1>(key), std::get<2>(key), index); + WARN(control,out).print("invalid index for filter key {},{},{}: {}\n", + ENUM_AS_STR(std::get<0>(key)), std::get<1>(key), std::get<2>(key), index); return; } item_filters[index] = filter; filter_config.set_str( serialize(item_filters, specials)); - DEBUG(control,out).print("updated item filter and persisted for key %d,%d,%d: %s\n", - std::get<0>(key), std::get<1>(key), std::get<2>(key), filter_config.get_str().c_str()); + DEBUG(control,out).print("updated item filter and persisted for key {},{},{}: {}\n", + ENUM_AS_STR(std::get<0>(key)), std::get<1>(key), std::get<2>(key), filter_config.get_str()); } diff --git a/plugins/buildingplan/itemfilter.cpp b/plugins/buildingplan/itemfilter.cpp index 0e4569f156..09a4a15c8e 100644 --- a/plugins/buildingplan/itemfilter.cpp +++ b/plugins/buildingplan/itemfilter.cpp @@ -41,7 +41,7 @@ static bool deserializeMaterialMask(const string& ser, df::dfhack_material_categ return true; if (!parseJobMaterialCategory(&mat_mask, ser)) { - DEBUG(control).print("invalid job material category serialization: '%s'", ser.c_str()); + DEBUG(control).print("invalid job material category serialization: '{}'", ser); return false; } return true; @@ -56,7 +56,7 @@ static bool deserializeMaterials(const string& ser, set &m for (auto m = mat_names.begin(); m != mat_names.end(); m++) { DFHack::MaterialInfo material; if (!material.find(*m) || !material.isValid()) { - DEBUG(control).print("invalid material name serialization: '%s'", ser.c_str()); + DEBUG(control).print("invalid material name serialization: '{}'", ser); return false; } materials.emplace(material); @@ -68,7 +68,7 @@ ItemFilter::ItemFilter(color_ostream &out, const string& serialized) : ItemFilte vector tokens; split_string(&tokens, serialized, "/"); if (tokens.size() < 5) { - DEBUG(control,out).print("invalid ItemFilter serialization: '%s'", serialized.c_str()); + DEBUG(control,out).print("invalid ItemFilter serialization: '{}'", serialized); return; } @@ -155,8 +155,8 @@ bool ItemFilter::matches(DFHack::MaterialInfo &material) const { bool ItemFilter::matches(df::item *item) const { int16_t quality = (item->flags.bits.artifact ? df::item_quality::Artifact : item->getQuality()); if (quality < min_quality || quality > max_quality) { - TRACE(cycle).print("item outside of quality range (%d not between %d and %d)\n", - quality, min_quality, max_quality); + TRACE(cycle).print("item outside of quality range ({} not between {} and {})\n", + ENUM_AS_STR(static_cast(quality)), ENUM_AS_STR(min_quality), ENUM_AS_STR(max_quality)); return false; } diff --git a/plugins/buildingplan/itemfilter.h b/plugins/buildingplan/itemfilter.h index 8a1f67d3cf..95a216c302 100644 --- a/plugins/buildingplan/itemfilter.h +++ b/plugins/buildingplan/itemfilter.h @@ -7,6 +7,8 @@ #include "df/dfhack_material_category.h" #include "df/item_quality.h" +#include + class ItemFilter { public: ItemFilter(); diff --git a/plugins/buildingplan/plannedbuilding.cpp b/plugins/buildingplan/plannedbuilding.cpp index 5888d169e7..e0f5a8984e 100644 --- a/plugins/buildingplan/plannedbuilding.cpp +++ b/plugins/buildingplan/plannedbuilding.cpp @@ -41,8 +41,8 @@ static vector> deserialize_vector_ids(color_ostre split_string(&rawstrs, bld_config.get_str(), "|"); const string &serialized = rawstrs[0]; - DEBUG(control,out).print("deserializing vector ids for building %d: %s\n", - bld_config.get_int(BLD_CONFIG_ID), serialized.c_str()); + DEBUG(control,out).print("deserializing vector ids for building {}: {}\n", + bld_config.get_int(BLD_CONFIG_ID), serialized); vector joined; split_string(&joined, serialized, ";"); @@ -100,12 +100,12 @@ PlannedBuilding::PlannedBuilding(color_ostream &out, df::building *bld, HeatSafe : id(bld->id), vector_ids(get_vector_ids(out, id)), heat_safety(heat), item_filters(item_filters.getItemFilters()), specials(item_filters.getSpecials()) { - DEBUG(control,out).print("creating persistent data for building %d\n", id); + DEBUG(control,out).print("creating persistent data for building {}\n", id); bld_config = World::AddPersistentSiteData(BLD_CONFIG_KEY); bld_config.set_int(BLD_CONFIG_ID, id); bld_config.set_int(BLD_CONFIG_HEAT, heat_safety); bld_config.set_str(serialize(vector_ids, item_filters)); - DEBUG(control,out).print("serialized state for building %d: %s\n", id, bld_config.get_str().c_str()); + DEBUG(control,out).print("serialized state for building {}: {}\n", id, bld_config.get_str()); } PlannedBuilding::PlannedBuilding(color_ostream &out, PersistentDataItem &bld_config) diff --git a/plugins/burrow.cpp b/plugins/burrow.cpp index d06e95efb6..172a7d37d2 100644 --- a/plugins/burrow.cpp +++ b/plugins/burrow.cpp @@ -1,3 +1,5 @@ +#include + #include "Debug.h" #include "LuaTools.h" #include "PluginManager.h" @@ -45,7 +47,7 @@ static void jobStartedHandler(color_ostream& out, void* ptr); static void jobCompletedHandler(color_ostream& out, void* ptr); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(status, out).print("initializing %s\n", plugin_name); + DEBUG(status, out).print("initializing {}\n", plugin_name); commands.push_back( PluginCommand("burrow", "Quickly adjust burrow tiles and units.", @@ -60,7 +62,7 @@ static void reset() { DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (enable != is_enabled) { is_enabled = enable; - DEBUG(status, out).print("%s from the API\n", is_enabled ? "enabled" : "disabled"); + DEBUG(status, out).print("{} from the API\n", is_enabled ? "enabled" : "disabled"); reset(); if (enable) { init_diggers(out); @@ -71,13 +73,13 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { } } else { - DEBUG(status, out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); + DEBUG(status, out).print("{} from the API, but already {}; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); } return CR_OK; } DFhackCExport command_result plugin_shutdown(color_ostream &out) { - DEBUG(status, out).print("shutting down %s\n", plugin_name); + DEBUG(status, out).print("shutting down {}\n", plugin_name); reset(); return CR_OK; } @@ -90,7 +92,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan static command_result do_command(color_ostream &out, vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -134,8 +136,8 @@ static void jobStartedHandler(color_ostream& out, void* ptr) { return; const df::coord &pos = job->pos; - DEBUG(event, out).print("dig job started: id=%d, pos=(%d,%d,%d), type=%s\n", - job->id, pos.x, pos.y, pos.z, ENUM_KEY_STR(job_type, job->job_type).c_str()); + DEBUG(event, out).print("dig job started: id={}, pos=({}, {}, {}), type={}\n", + job->id, pos.x, pos.y, pos.z, ENUM_KEY_STR(job_type, job->job_type)); df::tiletype *tt = Maps::getTileType(pos); if (tt) active_dig_jobs[pos] = *tt; @@ -197,8 +199,8 @@ static void jobCompletedHandler(color_ostream& out, void* ptr) { return; const df::coord &pos = job->pos; - DEBUG(event, out).print("dig job completed: id=%d, pos=(%d,%d,%d), type=%s\n", - job->id, pos.x, pos.y, pos.z, ENUM_KEY_STR(job_type, job->job_type).c_str()); + DEBUG(event, out).print("dig job completed: id={}, pos=({}, {}, {}), type={}\n", + job->id, pos.x, pos.y, pos.z, ENUM_KEY_STR(job_type, job->job_type)); df::tiletype prev_tt = active_dig_jobs[pos]; df::tiletype *tt = Maps::getTileType(pos); @@ -542,7 +544,7 @@ static void flood_fill(lua_State *L, bool enable) { bool start_outside = is_outside(start_pos, start_des); bool start_hidden = start_des->bits.hidden; uint16_t start_walk = Maps::getWalkableGroup(start_pos); - DEBUG(status).print("starting pos: (%d,%d,%d); outside: %d; hidden: %d\n", + DEBUG(status).print("starting pos: ({},{},{}); outside: {}; hidden: {}\n", start_pos.x, start_pos.y, start_pos.z, start_outside, start_hidden); std::stack flood; @@ -552,7 +554,7 @@ static void flood_fill(lua_State *L, bool enable) { const df::coord pos = flood.top(); flood.pop(); - TRACE(status).print("pos: (%d,%d,%d)\n", pos.x, pos.y, pos.z); + TRACE(status).print("pos: ({},{},{})\n", pos.x, pos.y, pos.z); df::tile_designation *des = Maps::getTileDesignation(pos); if (!des || diff --git a/plugins/changeitem.cpp b/plugins/changeitem.cpp index 33387aa141..e07db44570 100644 --- a/plugins/changeitem.cpp +++ b/plugins/changeitem.cpp @@ -236,7 +236,7 @@ command_result df_changeitem(color_ostream &out, vector & parameters) changeitem_execute(out, item, info, force, change_material, new_material, change_quality, new_quality, change_subtype, new_subtype); processed_total++; } - out.print("Done. %d items processed.\n", processed_total); + out.print("Done. {} items processed.\n", processed_total); } else { diff --git a/plugins/channel-safely/channel-groups.cpp b/plugins/channel-safely/channel-groups.cpp index c1a5b5953f..71cfdddb8d 100644 --- a/plugins/channel-safely/channel-groups.cpp +++ b/plugins/channel-safely/channel-groups.cpp @@ -111,7 +111,7 @@ void ChannelGroups::add(const df::coord &map_pos) { // we already have group "prime" if you will, so we're going to merge the new find into prime Group &group2 = groups.at(index2); // merge - TRACE(groups).print(" -> merging two groups. group 1 size: %zu. group 2 size: %zu\n", group->size(), + TRACE(groups).print(" -> merging two groups. group 1 size: {}. group 2 size: {}\n", group->size(), group2.size()); for (auto pos2: group2) { group->emplace(pos2); @@ -119,7 +119,7 @@ void ChannelGroups::add(const df::coord &map_pos) { } group2.clear(); free_spots.emplace(index2); - TRACE(groups).print(" merged size: %zu\n", group->size()); + TRACE(groups).print(" merged size: {}\n", group->size()); } } } @@ -143,13 +143,13 @@ void ChannelGroups::add(const df::coord &map_pos) { } // puts the "add" in "ChannelGroups::add" group->emplace(map_pos); - DEBUG(groups).print(" = group[%d] of (" COORD ") is size: %zu\n", group_index, COORDARGS(map_pos), group->size()); + DEBUG(groups).print(" = group[{}] of (" COORD ") is size: {}\n", group_index, COORDARGS(map_pos), group->size()); // we may have performed a merge, so we update all the `coord -> group index` mappings for (auto &wpos: *group) { groups_map[wpos] = group_index; } - DEBUG(groups).print(" <- add() exits, there are %zu mappings\n", groups_map.size()); + DEBUG(groups).print(" <- add() exits, there are {} mappings\n", groups_map.size()); } // scans a single tile for channel designations @@ -192,7 +192,7 @@ void ChannelGroups::scan(bool full_scan) { std::set gone_jobs; set_difference(last_jobs, jobs, gone_jobs); set_difference(jobs, last_jobs, new_jobs); - INFO(groups).print("gone jobs: %zd\nnew jobs: %zd\n",gone_jobs.size(), new_jobs.size()); + INFO(groups).print("gone jobs: {}\nnew jobs: {}\n",gone_jobs.size(), new_jobs.size()); for (auto &pos : new_jobs) { add(pos); } @@ -236,7 +236,7 @@ void ChannelGroups::scan(bool full_scan) { for (df::block_square_event* event: block->block_events) { if (auto evT = virtual_cast(event)) { // we want to let the user keep some designations free of being managed - TRACE(groups).print(" tile designation priority: %d\n", evT->priority[lx][ly]); + TRACE(groups).print(" tile designation priority: {}\n", evT->priority[lx][ly]); if (evT->priority[lx][ly] < 1000 * config.ignore_threshold) { if (empty_group) { group_blocks.emplace(block); @@ -338,9 +338,9 @@ void ChannelGroups::debug_groups() { int idx = 0; DEBUG(groups).print(" debugging group data\n"); for (auto &group: groups) { - DEBUG(groups).print(" group %d (size: %zu)\n", idx, group.size()); + DEBUG(groups).print(" group {} (size: {})\n", idx, group.size()); for (auto &pos: group) { - DEBUG(groups).print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z); + DEBUG(groups).print(" ({},{},{})\n", pos.x, pos.y, pos.z); } idx++; } @@ -350,9 +350,9 @@ void ChannelGroups::debug_groups() { // prints debug info group mappings void ChannelGroups::debug_map() { if (DFHack::debug_groups.isEnabled(DebugCategory::LTRACE)) { - INFO(groups).print("Group Mappings: %zu\n", groups_map.size()); + INFO(groups).print("Group Mappings: {}\n", groups_map.size()); for (auto &pair: groups_map) { - TRACE(groups).print(" map[" COORD "] = %d\n", COORDARGS(pair.first), pair.second); + TRACE(groups).print(" map[" COORD "] = {}\n", COORDARGS(pair.first), pair.second); } } } diff --git a/plugins/channel-safely/channel-manager.cpp b/plugins/channel-safely/channel-manager.cpp index 67f8742b38..6c85fda951 100644 --- a/plugins/channel-safely/channel-manager.cpp +++ b/plugins/channel-safely/channel-manager.cpp @@ -89,7 +89,7 @@ void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool // the tile below is not solid earth // we're counting accessibility then dealing with 0 access DEBUG(manager).print("analysis: cave-in condition found\n"); - INFO(manager).print("(%d) checking accessibility of (" COORD ") from (" COORD ")\n", cavein_possible,COORDARGS(pos),COORDARGS(miner_pos)); + INFO(manager).print("({}) checking accessibility of ({}) from ({})\n", cavein_possible,pos,miner_pos); auto access = count_accessibility(miner_pos, pos); if (access == 0) { TRACE(groups).print(" has 0 access\n"); @@ -100,7 +100,7 @@ void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool // todo: engage dig management, swap channel designations for dig } } else { - WARN(manager).print(" has %d access\n", access); + WARN(manager).print(" has {} access\n", access); cavein_possible = config.riskaverse; cavein_candidates.emplace(pos, access); least_access = std::min(access, least_access); @@ -110,13 +110,13 @@ void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool dig_now(DFHack::Core::getInstance().getConsole(), pos); } } - DEBUG(manager).print("cavein possible(%d)\n" - "%zu candidates\n" - "least access %d\n", cavein_possible, cavein_candidates.size(), least_access); + DEBUG(manager).print("cavein possible({})\n" + "{} candidates\n" + "least access {}\n", cavein_possible, cavein_candidates.size(), least_access); } // managing designations if (!group.empty()) { - DEBUG(manager).print("managing group #%d\n", groups.debugGIndex(*group.begin())); + DEBUG(manager).print("managing group #{}\n", groups.debugGIndex(*group.begin())); } for (auto &pos: group) { // if no cave-in is possible [or we don't check for], we'll just execute normally and move on @@ -132,7 +132,7 @@ void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool if (CSP::dignow_queue.count(pos) || (cavein_candidates.count(pos) && least_access < MAX && cavein_candidates[pos] <= least_access+OFFSET)) { - TRACE(manager).print("cave-in evaluated true and either of dignow or (%d <= %d)\n", cavein_candidates[pos], least_access+OFFSET); + TRACE(manager).print("cave-in evaluated true and either of dignow or ({} <= {})\n", cavein_candidates[pos], least_access+OFFSET); df::coord local(pos); local.x %= 16; local.y %= 16; @@ -143,7 +143,7 @@ void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool // we want to let the user keep some designations free of being managed auto b = std::max(0, cavein_candidates[pos] - least_access); auto v = 1000 + (b * 1700); - DEBUG(manager).print("(" COORD ") 1000+1000(%d) -> %d {least-access: %d}\n",COORDARGS(pos), b, v, least_access); + DEBUG(manager).print("({}) 1000+1000({}) -> {} {least-access: {}}\n", pos, b, v, least_access); evT->priority[Coord(local)] = v; } } @@ -152,7 +152,7 @@ void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool } // cavein possible, but we failed to meet the criteria for activation if (cavein_candidates.count(pos)) { - DEBUG(manager).print("cave-in evaluated true and the cavein candidate's accessibility check was made as (%d <= %d)\n", cavein_candidates[pos], least_access+OFFSET); + DEBUG(manager).print("cave-in evaluated true and the cavein candidate's accessibility check was made as ({} <= {})\n", cavein_candidates[pos], least_access+OFFSET); } else { DEBUG(manager).print("cave-in evaluated true and the position was not a candidate, nor was it set for dignow\n"); } @@ -163,7 +163,7 @@ void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool bool ChannelManager::manage_one(const df::coord &map_pos, bool set_marker_mode, bool marker_mode) const { if (Maps::isValidTilePos(map_pos)) { - TRACE(manager).print("manage_one((" COORD "), %d, %d)\n", COORDARGS(map_pos), set_marker_mode, marker_mode); + TRACE(manager).print("manage_one(({}), {}, {})\n", map_pos, set_marker_mode, marker_mode); df::map_block* block = Maps::getTileBlock(map_pos); // we calculate the position inside the block* df::coord local(map_pos); @@ -188,7 +188,7 @@ bool ChannelManager::manage_one(const df::coord &map_pos, bool set_marker_mode, block->flags.bits.designated = true; } tile_occupancy.bits.dig_marked = marker_mode; - TRACE(manager).print("marker mode %s\n", marker_mode ? "ENABLED" : "DISABLED"); + TRACE(manager).print("marker mode {}\n", marker_mode ? "ENABLED" : "DISABLED"); } else { // if we are though, it should be totally safe to dig tile_occupancy.bits.dig_marked = false; diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index 4aa1648453..ac69ffdc8e 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -182,7 +182,7 @@ namespace CSP { psetting.ival(IGNORE_THRESH) = config.ignore_threshold; psetting.ival(FALL_THRESH) = config.fall_threshold; } catch (std::exception &e) { - ERR(plugin).print("%s\n", e.what()); + ERR(plugin).print("{}\n", e.what()); } } } @@ -208,7 +208,7 @@ namespace CSP { config.refresh_freq = psetting.ival(REFRESH_RATE); config.monitor_freq = psetting.ival(MONITOR_RATE); } catch (std::exception &e) { - ERR(plugin).print("%s\n", e.what()); + ERR(plugin).print("{}\n", e.what()); } } active_workers.clear(); @@ -320,14 +320,14 @@ namespace CSP { dignow_queue.emplace(report->pos); } - DEBUG(plugin).print("%d, pos: " COORD ", pos2: " COORD "\n%s\n", report_id, COORDARGS(report->pos), - COORDARGS(report->pos2), report->text.c_str()); + DEBUG(plugin).print("{}, pos: {} , pos2: {}\n{}\n", report_id, report->pos, + report->pos2, report->text.c_str()); } break; case announcement_type::CAVE_COLLAPSE: if (config.resurrect) { - DEBUG(plugin).print("CAVE IN\n%d, pos: " COORD ", pos2: " COORD "\n%s\n", report_id, COORDARGS(report->pos), - COORDARGS(report->pos2), report->text.c_str()); + DEBUG(plugin).print("CAVE IN\n{}, pos: {} , pos2: {}\n{}\n", report_id, report->pos, + report->pos2, report->text.c_str()); df::coord below = report->pos; below.z -= 1; @@ -344,12 +344,12 @@ namespace CSP { Units::getUnitsInBox(units, COORDARGS(areaMin), COORDARGS(areaMax)); for (auto unit: units) { endangered_units[unit] = tick; - DEBUG(plugin).print(" [id %d] was near a cave in.\n", unit->id); + DEBUG(plugin).print(" [id {}] was near a cave in.\n", unit->id); } for (auto unit : world->units.all) { if (last_safe.count(unit->id)) { endangered_units[unit] = tick; - DEBUG(plugin).print(" [id %d] is/was a worker, we'll track them too.\n", unit->id); + DEBUG(plugin).print(" [id {}] is/was a worker, we'll track them too.\n", unit->id); } } } @@ -472,7 +472,7 @@ namespace CSP { // clean up any "endangered" workers that have been tracked 100 ticks or more for (auto iter = endangered_units.begin(); iter != endangered_units.end();) { if (tick - iter->second >= 1200) { //keep watch 1 day - DEBUG(plugin).print("It has been one day since [id %d]'s last incident.\n", iter->first->id); + DEBUG(plugin).print("It has been one day since [id {}]'s last incident.\n", iter->first->id); iter = endangered_units.erase(iter); continue; } @@ -525,7 +525,7 @@ DFhackCExport command_result plugin_load_site_data (color_ostream &out) { DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (!Core::getInstance().isMapLoaded() || !World::IsSiteLoaded()) { - out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + outs without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -582,7 +582,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_ev command_result channel_safely(color_ostream &out, std::vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::IsSiteLoaded()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + outs without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -650,23 +650,23 @@ command_result channel_safely(color_ostream &out, std::vector ¶ return DFHack::CR_WRONG_USAGE; } } catch (const std::exception &e) { - out.printerr("%s\n", e.what()); + out.printerr("{}\n", e.what()); return DFHack::CR_FAILURE; } } } else { - out.print("Channel-Safely is %s\n", enabled ? "ENABLED." : "DISABLED."); + out.print("Channel-Safely is {}\n", enabled ? "ENABLED." : "DISABLED."); out.print(" FEATURES:\n"); - out.print(" %-20s\t%s\n", "risk-averse: ", config.riskaverse ? "on." : "off."); - out.print(" %-20s\t%s\n", "monitoring: ", config.monitoring ? "on." : "off."); - out.print(" %-20s\t%s\n", "require-vision: ", config.require_vision ? "on." : "off."); + out.print(" {:<20}\t{}\n", "risk-averse: ", config.riskaverse ? "on." : "off."); + out.print(" {:<20}\t{}\n", "monitoring: ", config.monitoring ? "on." : "off."); + out.print(" {:<20}\t{}\n", "require-vision: ", config.require_vision ? "on." : "off."); //out.print(" %-20s\t%s\n", "insta-dig: ", config.insta_dig ? "on." : "off."); - out.print(" %-20s\t%s\n", "resurrect: ", config.resurrect ? "on." : "off."); + out.print(" {:<20}\t{}\n", "resurrect: ", config.resurrect ? "on." : "off."); out.print(" SETTINGS:\n"); - out.print(" %-20s\t%" PRIi32 "\n", "refresh-freq: ", config.refresh_freq); - out.print(" %-20s\t%" PRIi32 "\n", "monitor-freq: ", config.monitor_freq); - out.print(" %-20s\t%" PRIu8 "\n", "ignore-threshold: ", config.ignore_threshold); - out.print(" %-20s\t%" PRIu8 "\n", "fall-threshold: ", config.fall_threshold); + out.print(" {:<20}\t{}\n", "refresh-freq: ", config.refresh_freq); + out.print(" {:<20}\t{}\n", "monitor-freq: ", config.monitor_freq); + out.print(" {:<20}\t{}\n", "ignore-threshold: ", config.ignore_threshold); + out.print(" {:<20}\t{}\n", "fall-threshold: ", config.fall_threshold); } CSP::SaveSettings(); return DFHack::CR_OK; diff --git a/plugins/channel-safely/include/inlines.h b/plugins/channel-safely/include/inlines.h index 362fd927a3..16db1ff7d2 100644 --- a/plugins/channel-safely/include/inlines.h +++ b/plugins/channel-safely/include/inlines.h @@ -311,7 +311,7 @@ inline bool dig_now(color_ostream &out, const df::coord &map_pos) { // fully heals the unit specified, resurrecting if need be inline void resurrect(color_ostream &out, const int32_t &unit) { out.color(DFHack::COLOR_RED); - out.print("channel-safely: resurrecting [id: %d]\n", unit); + out.print("channel-safely: resurrecting [id: {}]\n", unit); std::vector params{"-r", "--unit", std::to_string(unit)}; Core::getInstance().runCommand(out,"full-heal", params); } diff --git a/plugins/cleanconst.cpp b/plugins/cleanconst.cpp index b72806cd9b..9e05ecae46 100644 --- a/plugins/cleanconst.cpp +++ b/plugins/cleanconst.cpp @@ -38,13 +38,17 @@ command_result df_cleanconst(color_ostream &out, vector & parameters) df::construction *cons = df::construction::find(pos); if (!cons) { - out.printerr("Item at %i,%i,%i marked as construction but no construction is present!\n", pos.x, pos.y, pos.z); + out.printerr("Item at {} marked as construction but no construction is present!\n", pos); continue; } // if the construction is already labeled as "no build item", then leave it alone if (cons->flags.bits.no_build_item) continue; + // Skip reinforced constructions as well + if (cons->flags.bits.reinforced) + continue; + // only destroy the item if the construction claims to be made of the exact same thing if (item->getType() != cons->item_type || item->getSubtype() != cons->item_subtype || @@ -58,7 +62,7 @@ command_result df_cleanconst(color_ostream &out, vector & parameters) cleaned_total++; } - out.print("Done. %d construction items cleaned up.\n", cleaned_total); + out.print("Done. {} construction items cleaned up.\n", cleaned_total); return CR_OK; } diff --git a/plugins/cleaners.cpp b/plugins/cleaners.cpp index 6706bc92d8..74bc40e6ff 100644 --- a/plugins/cleaners.cpp +++ b/plugins/cleaners.cpp @@ -95,7 +95,7 @@ command_result cleanmap (color_ostream &out, bool snow, bool mud, bool item_spat } if(num_blocks) - out.print("Cleaned %d of %zd map blocks.\n", num_blocks, world->map.map_blocks.size()); + out.print("Cleaned {} of {} map blocks.\n", num_blocks, world->map.map_blocks.size()); return CR_OK; } @@ -123,7 +123,7 @@ command_result cleanitems (color_ostream &out) } } if (cleaned_total) - out.print("Removed %d contaminants from %d items.\n", cleaned_total, cleaned_items); + out.print("Removed {} contaminants from {} items.\n", cleaned_total, cleaned_items); return CR_OK; } @@ -143,7 +143,7 @@ command_result cleanunits (color_ostream &out) } } if (cleaned_total) - out.print("Removed %d contaminants from %d creatures.\n", cleaned_total, cleaned_units); + out.print("Removed {} contaminants from {} creatures.\n", cleaned_total, cleaned_units); return CR_OK; } @@ -163,7 +163,7 @@ command_result cleanplants (color_ostream &out) } } if (cleaned_total) - out.print("Removed %d contaminants from %d plants.\n", cleaned_total, cleaned_plants); + out.print("Removed {} contaminants from {} plants.\n", cleaned_total, cleaned_plants); return CR_OK; } diff --git a/plugins/cleanowned.cpp b/plugins/cleanowned.cpp index 9e6ddb9849..c8d67d92ad 100644 --- a/plugins/cleanowned.cpp +++ b/plugins/cleanowned.cpp @@ -47,7 +47,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) command_result df_cleanowned (color_ostream &out, vector & parameters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -155,16 +155,16 @@ command_result df_cleanowned (color_ostream &out, vector & parameters) std::string description; item->getItemDescription(&description, 0); out.print( - "[%d] %s (wear level %d)", + "[{}] {} (wear level {})", item->id, - DF2CONSOLE(description).c_str(), + DF2CONSOLE(description), item->getWear() ); df::unit *owner = Items::getOwner(item); if (owner) - out.print(", owner %s", DF2CONSOLE(Units::getReadableName(owner)).c_str()); + out.print(", owner {}", DF2CONSOLE(Units::getReadableName(owner))); if (!dry_run) { diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index fba821b270..d813d8e44b 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -144,7 +144,7 @@ static inline bool select_caste_mat(color_ostream &out, vector &tokens, out.printerr("You must also specify a caste.\n"); else out.printerr("The creature you specified has no such caste!\n"); - out.printerr("Valid castes:%s\n", castes.c_str()); + out.printerr("Valid castes:{}\n", castes); return false; } } @@ -198,7 +198,7 @@ static inline bool select_plant_growth(color_ostream &out, vector &token out.printerr("You must also specify a growth ID.\n"); else out.printerr("The plant you specified has no such growth!\n"); - out.printerr("Valid growths:%s\n", growths.c_str()); + out.printerr("Valid growths:{}\n", growths); return false; } } @@ -226,7 +226,7 @@ command_result df_createitem (color_ostream &out, vector ¶meters) { ItemTypeInfo iinfo(item->getType(), item->getSubtype()); MaterialInfo minfo(item->getMaterial(), item->getMaterialIndex()); - out.print("%s %s\n", iinfo.getToken().c_str(), minfo.getToken().c_str()); + out.print("{} {}\n", iinfo.getToken(), minfo.getToken()); return CR_OK; } else if (parameters[0] == "floor") { @@ -268,7 +268,7 @@ command_result df_createitem (color_ostream &out, vector ¶meters) { dest_container = item->id; string name; item->getItemDescription(&name, 0); - out.print("Items created will be placed inside %s.\n", name.c_str()); + out.print("Items created will be placed inside {}.\n", name); return CR_OK; } else if (parameters[0] == "building") { @@ -280,6 +280,7 @@ command_result df_createitem (color_ostream &out, vector ¶meters) { } switch (building->getType()) { using namespace df::enums::building_type; + case Table: case Coffin: case Furnace: case TradeDepot: @@ -294,8 +295,12 @@ command_result df_createitem (color_ostream &out, vector ¶meters) { case AnimalTrap: case Cage: case Wagon: + case Nest: case NestBox: case Hive: + case Bookcase: + case DisplayFurniture: + case OfferingPlace: break; default: out.printerr("The selected building cannot be used for item storage!\n"); @@ -308,7 +313,7 @@ command_result df_createitem (color_ostream &out, vector ¶meters) { dest_building = building->id; string name; building->getName(&name); - out.print("Items created will be placed inside %s.\n", name.c_str()); + out.print("Items created will be placed inside {}.\n", name); return CR_OK; } else diff --git a/plugins/cursecheck.cpp b/plugins/cursecheck.cpp index 0762ad9c36..f0651889a6 100644 --- a/plugins/cursecheck.cpp +++ b/plugins/cursecheck.cpp @@ -182,10 +182,10 @@ command_result cursecheck(color_ostream& out, vector & parameters) out << DF2CONSOLE(Units::getReadableName(unit)); auto death = df::incident::find(unit->counters.death_id); - out.print(", born in %d, cursed in %d to be a %s. (%s%s)\n", + out.print(", born in {}, cursed in {} to be a {}. ({}{})\n", unit->birth_year, unit->curse_year, - curse_names[cursetype].c_str(), + curse_names[cursetype], // technically most cursed creatures are undead, // therefore output 'active' if they are not completely dead unit->flags2.bits.killed ? "deceased" : "active", @@ -203,15 +203,15 @@ command_result cursecheck(color_ostream& out, vector & parameters) if (giveUnitID) { - out.print("Creature %d, race %d (%x)\n", unit->id, unit->race, unit->race); + out.print("Creature {}, race {} (0x{:x})\n", unit->id, unit->race, unit->race); } } } if (selected_unit && !giveDetails) - out.print("Selected unit is %scursed\n", cursecount == 0 ? "not " : ""); + out.print("Selected unit is {}cursed\n", cursecount == 0 ? "not " : ""); else if (!selected_unit) - out.print("%zd cursed creatures on map\n", cursecount); + out.print("{} cursed creatures on map\n", cursecount); return CR_OK; } diff --git a/plugins/cxxrandom.cpp b/plugins/cxxrandom.cpp index 3f6a405598..a471cda6be 100644 --- a/plugins/cxxrandom.cpp +++ b/plugins/cxxrandom.cpp @@ -140,7 +140,7 @@ class NumberSequence } void Print() { for( auto v : m_numbers ) { - cout->print( "%" PRId64 " ", v ); + cout->print("{} ", v); } } }; diff --git a/plugins/debug.cpp b/plugins/debug.cpp index eb2fc4c1c7..d0e5e0f5a0 100644 --- a/plugins/debug.cpp +++ b/plugins/debug.cpp @@ -21,6 +21,7 @@ redistribute it freely, subject to the following restrictions: distribution. */ +#include "Core.h" #include "PluginManager.h" #include "DebugManager.h" #include "Debug.h" @@ -254,10 +255,10 @@ struct Filter { private: std::regex category_; std::regex plugin_; - DebugCategory::level level_; - size_t matches_; - bool persistent_; - bool enabled_; + DebugCategory::level level_{DebugCategory::level::LTRACE}; + size_t matches_{0}; + bool persistent_{false}; + bool enabled_{false}; std::string categoryText_; std::string pluginText_; }; @@ -352,7 +353,9 @@ struct FilterManager : public std::map //! Current configuration version implemented by the code constexpr static Json::UInt configVersion{1}; //! Path to the configuration file - constexpr static const char* configPath{"dfhack-config/runtime-debug.json"}; + const inline std::filesystem::path getConfigPath() const { + return DFHack::Core::getInstance().getConfigPath() / "runtime-debug.json"; + } //! Get reference to the singleton static FilterManager& getInstance() noexcept @@ -434,8 +437,6 @@ struct FilterManager : public std::map DebugManager::categorySignal_t::Connection connection_; }; -constexpr const char* FilterManager::configPath; - FilterManager::~FilterManager() { } @@ -443,6 +444,7 @@ FilterManager::~FilterManager() command_result FilterManager::loadConfig(DFHack::color_ostream& out) noexcept { nextId_ = 1; + auto configPath = getConfigPath(); if (!Filesystem::isfile(configPath)) return CR_OK; try { @@ -463,6 +465,7 @@ command_result FilterManager::loadConfig(DFHack::color_ostream& out) noexcept command_result FilterManager::saveConfig(DFHack::color_ostream& out) const noexcept { + auto configPath = getConfigPath(); try { DEBUG(command, out) << "Save config to '" << configPath << "'" << std::endl; JsonArchive archive; @@ -804,9 +807,9 @@ static command_result setFilter(color_ostream& out, return v.match(level); }); if (iter == levelNames.end()) { - ERR(command,out).print("level ('%s') parameter must be one of " + ERR(command,out).print("level ('{}') parameter must be one of " "trace, debug, info, warning, error.\n", - parameters[pos].c_str()); + parameters[pos]); return CR_WRONG_USAGE; } @@ -1062,8 +1065,8 @@ CommandDispatch::dispatch_t CommandDispatch::dispatch { static command_result commandDebugFilter(color_ostream& out, std::vector& parameters) { - DEBUG(command,out).print("debugfilter %s, parameter count %zu\n", - parameters.size() > 0 ? parameters[0].c_str() : "", + DEBUG(command,out).print("debugfilter {}, parameter count {}\n", + parameters.size() > 0 ? parameters[0] : "", parameters.size()); auto iter = CommandDispatch::dispatch.end(); if (0u < parameters.size()) @@ -1096,7 +1099,7 @@ DFhackCExport DFHack::command_result plugin_init(DFHack::color_ostream& out, filter.apply(*cat); } } - INFO(init,out).print("plugin_init with %zu commands, %zu filters and %zu categories\n", + INFO(init,out).print("plugin_init with {}, {} filters and {} categories\n", commands.size(), filMan.size(), catMan.size()); filMan.connectTo(catMan.categorySignal); return rv; diff --git a/plugins/deramp.cpp b/plugins/deramp.cpp index 726693b416..6b41701225 100644 --- a/plugins/deramp.cpp +++ b/plugins/deramp.cpp @@ -98,9 +98,9 @@ command_result df_deramp (color_ostream &out, vector & parameters) } } if (count) - out.print("Found and changed %d tiles.\n", count); + out.print("Found and changed {} tiles.\n", count); if (countbad) - out.print("Fixed %d bad down ramps.\n", countbad); + out.print("Fixed {} bad down ramps.\n", countbad); return CR_OK; } diff --git a/plugins/devel/buildprobe.cpp b/plugins/devel/buildprobe.cpp index 333f1d3bc4..3b4df00e28 100644 --- a/plugins/devel/buildprobe.cpp +++ b/plugins/devel/buildprobe.cpp @@ -55,7 +55,7 @@ command_result readFlag (color_ostream &out, vector & parameters) MapExtras::MapCache * MCache = new MapExtras::MapCache(); t_occupancy oc = MCache->occupancyAt(cursor); - out.print("Current Value: %d\n", oc.bits.building); + out.print("Current Value: {}\n", ENUM_AS_STR(oc.bits.building)); return CR_OK; } diff --git a/plugins/devel/check-structures-sanity/dispatch.cpp b/plugins/devel/check-structures-sanity/dispatch.cpp index 0c0b3453b4..162ea7bbd4 100644 --- a/plugins/devel/check-structures-sanity/dispatch.cpp +++ b/plugins/devel/check-structures-sanity/dispatch.cpp @@ -118,7 +118,7 @@ void Checker::queue_globals() // offset is the position of the DFHack pointer to this global. auto ptr = *reinterpret_cast(field->offset); - QueueItem item(stl_sprintf("df.global.%s", field->name), ptr); + QueueItem item(fmt::format("df.global.{}", field->name), ptr); CheckedStructure cs(field); if (!ptr) @@ -886,7 +886,7 @@ void Checker::check_stl_string(const QueueItem & item) else if (is_gcc && length > 0 && !is_valid_dereference(QueueItem(item, "?start?", reinterpret_cast(string->start)), 1)) { // nullptr is NOT okay here - FAIL("invalid string pointer " << stl_sprintf("0x%" PRIxPTR, string->start)); + FAIL("invalid string pointer " << fmt::format("{:#x}", string->start)); return; } else if (is_local && length >= 16) diff --git a/plugins/devel/check-structures-sanity/main.cpp b/plugins/devel/check-structures-sanity/main.cpp index 5bc7c09d6d..6110afe1c0 100644 --- a/plugins/devel/check-structures-sanity/main.cpp +++ b/plugins/devel/check-structures-sanity/main.cpp @@ -77,7 +77,7 @@ static command_result command(color_ostream & out, std::vector & pa } \ catch (std::exception & ex) \ { \ - out.printerr("check-structures-sanity: argument to -%s: %s\n", #name, ex.what()); \ + out.printerr("check-structures-sanity: argument to -{}: {}\n", #name, ex.what()); \ return CR_WRONG_USAGE; \ } \ } diff --git a/plugins/devel/check-structures-sanity/types.cpp b/plugins/devel/check-structures-sanity/types.cpp index 647fa6141e..4c8a8f6ae2 100644 --- a/plugins/devel/check-structures-sanity/types.cpp +++ b/plugins/devel/check-structures-sanity/types.cpp @@ -10,7 +10,7 @@ QueueItem::QueueItem(const QueueItem & parent, const std::string & member, const { } QueueItem::QueueItem(const QueueItem & parent, size_t index, const void *ptr) : - QueueItem(parent.path + stl_sprintf("[%zu]", index), ptr) + QueueItem(parent.path + fmt::format("[{}]", index), ptr) { } diff --git a/plugins/devel/check-structures-sanity/validate.cpp b/plugins/devel/check-structures-sanity/validate.cpp index 24d5327f92..d6137eff1f 100644 --- a/plugins/devel/check-structures-sanity/validate.cpp +++ b/plugins/devel/check-structures-sanity/validate.cpp @@ -35,10 +35,10 @@ bool Checker::is_valid_dereference(const QueueItem & item, const CheckedStructur // assumes MALLOC_PERTURB_=45 #ifdef DFHACK64 #define UNINIT_PTR 0xd2d2d2d2d2d2d2d2 -#define FAIL_PTR(message) FAIL(stl_sprintf("0x%016zx: ", reinterpret_cast(base)) << message) +#define FAIL_PTR(message) FAIL(fmt::format("{:#016x} ", reinterpret_cast(base)) << message) #else #define UNINIT_PTR 0xd2d2d2d2 -#define FAIL_PTR(message) FAIL(stl_sprintf("0x%08zx: ", reinterpret_cast(base)) << message) +#define FAIL_PTR(message) FAIL(fmt::format("{:#016x} ", reinterpret_cast(base)) << message) #endif if (uintptr_t(base) == UNINIT_PTR) { diff --git a/plugins/devel/color-dfhack-text.cpp b/plugins/devel/color-dfhack-text.cpp index a19a475bb3..a17ad9d072 100644 --- a/plugins/devel/color-dfhack-text.cpp +++ b/plugins/devel/color-dfhack-text.cpp @@ -114,7 +114,7 @@ command_result color(color_ostream &out, std::vector ¶ms) } else if (p != "enable") { - out.printerr("Unrecognized option: %s\n", p.c_str()); + out.printerr("Unrecognized option: {}\n", p); return CR_WRONG_USAGE; } } diff --git a/plugins/devel/counters.cpp b/plugins/devel/counters.cpp index d3672d8d48..05c2024acc 100644 --- a/plugins/devel/counters.cpp +++ b/plugins/devel/counters.cpp @@ -22,7 +22,7 @@ command_result df_counters (color_ostream &out, vector & parameters) for (size_t i = 0; i < counters.size(); i++) { auto counter = counters[i]; - out.print("%i (%s): %i\n", (int)counter->id, ENUM_KEY_STR(misc_trait_type, counter->id).c_str(), counter->value); + out.print("{} ({}) : {}\n", (int)counter->id, ENUM_KEY_STR(misc_trait_type, counter->id), counter->value); } return CR_OK; diff --git a/plugins/devel/dumpmats.cpp b/plugins/devel/dumpmats.cpp index b910b6546b..91b70a2676 100644 --- a/plugins/devel/dumpmats.cpp +++ b/plugins/devel/dumpmats.cpp @@ -32,7 +32,7 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters) df::material *mat = world->raws.mat_table.builtin[mat_num]; if (!mat) continue; - out.print("\n[MATERIAL:%s] - reconstructed from data extracted from memory\n", mat->id.c_str()); + out.print("\n[MATERIAL:{}] - reconstructed from data extracted from memory\n", mat->id); int32_t def_color[6] = {-1,-1,-1,-1,-1,-1}; string def_name[6]; @@ -52,10 +52,10 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters) { def_color[matter_state::Liquid] = solid_color; def_color[matter_state::Gas] = solid_color; - out.print("\t[STATE_COLOR:ALL:%s]\n", world->raws.descriptors.colors[solid_color]->id.c_str()); + out.print("\t[STATE_COLOR:ALL:{}]\n", world->raws.descriptors.colors[solid_color]->id); } else - out.print("\t[STATE_COLOR:ALL_SOLID:%s]\n", world->raws.descriptors.colors[solid_color]->id.c_str()); + out.print("\t[STATE_COLOR:ALL_SOLID:{}]\n", world->raws.descriptors.colors[solid_color]->id); } string solid_name = mat->state_name[matter_state::Solid]; @@ -82,10 +82,10 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters) def_name[matter_state::Gas] = solid_name; def_adj[matter_state::Liquid] = solid_name; def_adj[matter_state::Gas] = solid_name; - out.print("\t[STATE_NAME_ADJ:ALL:%s]\n", solid_name.c_str()); + out.print("\t[STATE_NAME_ADJ:ALL:{}]\n", solid_name); } else - out.print("\t[STATE_NAME_ADJ:ALL_SOLID:%s]\n", solid_name.c_str()); + out.print("\t[STATE_NAME_ADJ:ALL_SOLID:{}]\n", solid_name); } } else @@ -104,10 +104,10 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters) { def_name[matter_state::Liquid] = solid_name; def_name[matter_state::Gas] = solid_name; - out.print("\t[STATE_NAME:ALL:%s]\n", solid_name.c_str()); + out.print("\t[STATE_NAME:ALL:{}]\n", solid_name); } else - out.print("\t[STATE_NAME:ALL_SOLID:%s]\n", solid_name.c_str()); + out.print("\t[STATE_NAME:ALL_SOLID:{}]\n", solid_name); } if (solid_adj == mat->state_adj[matter_state::Powder] || solid_adj == mat->state_adj[matter_state::Paste] || @@ -123,10 +123,10 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters) { def_adj[matter_state::Liquid] = solid_adj; def_adj[matter_state::Gas] = solid_adj; - out.print("\t[STATE_ADJ:ALL:%s]\n", solid_adj.c_str()); + out.print("\t[STATE_ADJ:ALL:{}]\n", solid_adj); } else - out.print("\t[STATE_ADJ:ALL_SOLID:%s]\n", solid_adj.c_str()); + out.print("\t[STATE_ADJ:ALL_SOLID:{}]\n", solid_adj); } } const char *state_names[6] = {"SOLID", "LIQUID", "GAS", "SOLID_POWDER", "SOLID_PASTE", "SOLID_PRESSED"}; @@ -134,110 +134,110 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters) FOR_ENUM_ITEMS(matter_state, state) { if (mat->state_color[state] != -1 && mat->state_color[state] != def_color[state]) - out.print("\t[STATE_COLOR:%s:%s]\n", state_names[state], world->raws.descriptors.colors[mat->state_color[state]]->id.c_str()); + out.print("\t[STATE_COLOR:{}:{}]\n", state_names[state], world->raws.descriptors.colors[mat->state_color[state]]->id); if (mat->state_name[state] == mat->state_adj[state]) { if ((mat->state_name[state].size() && mat->state_name[state] != def_name[state]) || (mat->state_adj[state].size() && mat->state_adj[state] != def_adj[state])) - out.print("\t[STATE_NAME_ADJ:%s:%s]\n", state_names[state], mat->state_name[state].c_str()); + out.print("\t[STATE_NAME_ADJ:{}:{}]\n", state_names[state], mat->state_name[state]); } else { if (mat->state_name[state].size() && mat->state_name[state] != def_name[state]) - out.print("\t[STATE_NAME:%s:%s]\n", state_names[state], mat->state_name[state].c_str()); + out.print("\t[STATE_NAME:{}:{}]\n", state_names[state], mat->state_name[state]); if (mat->state_adj[state].size() && mat->state_adj[state] != def_adj[state]) - out.print("\t[STATE_ADJ:%s:%s]\n", state_names[state], mat->state_adj[state].c_str()); + out.print("\t[STATE_ADJ:{}:{}]\n", state_names[state], mat->state_adj[state]); } } if (mat->basic_color[0] != 7 || mat->basic_color[1] != 0) - out.print("\t[BASIC_COLOR:%i:%i]\n", mat->basic_color[0], mat->basic_color[1]); + out.print("\t[BASIC_COLOR:{}:{}]\n", mat->basic_color[0], mat->basic_color[1]); if (mat->build_color[0] != 7 || mat->build_color[1] != 7 || mat->build_color[2] != 0) - out.print("\t[BUILD_COLOR:%i:%i:%i]\n", mat->build_color[0], mat->build_color[1], mat->build_color[2]); + out.print("\t[BUILD_COLOR:{}:{}:{}]\n", mat->build_color[0], mat->build_color[1], mat->build_color[2]); if (mat->tile_color[0] != 7 || mat->tile_color[1] != 7 || mat->tile_color[2] != 0) - out.print("\t[TILE_COLOR:%i:%i:%i]\n", mat->tile_color[0], mat->tile_color[1], mat->tile_color[2]); + out.print("\t[TILE_COLOR:{}:{}:{}]\n", mat->tile_color[0], mat->tile_color[1], mat->tile_color[2]); if (mat->tile != 0xdb) - out.print("\t[TILE:%i]\n", mat->tile); + out.print("\t[TILE:{}]\n", mat->tile); if (mat->item_symbol != 0x07) - out.print("\t[ITEM_SYMBOL:%i]\n", mat->item_symbol); + out.print("\t[ITEM_SYMBOL:{}]\n", mat->item_symbol); if (mat->material_value != 1) - out.print("\t[MATERIAL_VALUE:%i]\n", mat->material_value); + out.print("\t[MATERIAL_VALUE:{}]\n", mat->material_value); if (mat->gem_name1.size()) - out.print("\t[IS_GEM:%s:%s]\n", mat->gem_name1.c_str(), mat->gem_name2.c_str()); + out.print("\t[IS_GEM:{}:{}]\n", mat->gem_name1.c_str(), mat->gem_name2.c_str()); if (mat->stone_name.size()) - out.print("\t[STONE_NAME:%s]\n", mat->stone_name.c_str()); + out.print("\t[STONE_NAME:{}]\n", mat->stone_name.c_str()); if (mat->heat.spec_heat != 60001) - out.print("\t[SPEC_HEAT:%i]\n", mat->heat.spec_heat); + out.print("\t[SPEC_HEAT:{}]\n", mat->heat.spec_heat); if (mat->heat.heatdam_point != 60001) - out.print("\t[HEATDAM_POINT:%i]\n", mat->heat.heatdam_point); + out.print("\t[HEATDAM_POINT:{}]\n", mat->heat.heatdam_point); if (mat->heat.colddam_point != 60001) - out.print("\t[COLDDAM_POINT:%i]\n", mat->heat.colddam_point); + out.print("\t[COLDDAM_POINT:{}]\n", mat->heat.colddam_point); if (mat->heat.ignite_point != 60001) - out.print("\t[IGNITE_POINT:%i]\n", mat->heat.ignite_point); + out.print("\t[IGNITE_POINT:{}]\n", mat->heat.ignite_point); if (mat->heat.melting_point != 60001) - out.print("\t[MELTING_POINT:%i]\n", mat->heat.melting_point); + out.print("\t[MELTING_POINT:{}]\n", mat->heat.melting_point); if (mat->heat.boiling_point != 60001) - out.print("\t[BOILING_POINT:%i]\n", mat->heat.boiling_point); + out.print("\t[BOILING_POINT:{}]\n", mat->heat.boiling_point); if (mat->heat.mat_fixed_temp != 60001) - out.print("\t[MAT_FIXED_TEMP:%i]\n", mat->heat.mat_fixed_temp); + out.print("\t[MAT_FIXED_TEMP:{}]\n", mat->heat.mat_fixed_temp); if (uint32_t(mat->solid_density) != 0xFBBC7818) - out.print("\t[SOLID_DENSITY:%i]\n", mat->solid_density); + out.print("\t[SOLID_DENSITY:{}]\n", mat->solid_density); if (uint32_t(mat->liquid_density) != 0xFBBC7818) - out.print("\t[LIQUID_DENSITY:%i]\n", mat->liquid_density); + out.print("\t[LIQUID_DENSITY:{}]\n", mat->liquid_density); if (uint32_t(mat->molar_mass) != 0xFBBC7818) - out.print("\t[MOLAR_MASS:%i]\n", mat->molar_mass); + out.print("\t[MOLAR_MASS:{}]\n", mat->molar_mass); FOR_ENUM_ITEMS(strain_type, strain) { auto name = ENUM_KEY_STR(strain_type,strain); if (mat->strength.yield[strain] != 10000) - out.print("\t[%s_YIELD:%i]\n", name.c_str(), mat->strength.yield[strain]); + out.print("\t[{}_YIELD:{}]\n", name, mat->strength.yield[strain]); if (mat->strength.fracture[strain] != 10000) - out.print("\t[%s_FRACTURE:%i]\n", name.c_str(), mat->strength.fracture[strain]); + out.print("\t[{}_FRACTURE:{}]\n", name, mat->strength.fracture[strain]); if (mat->strength.strain_at_yield[strain] != 0) - out.print("\t[%s_STRAIN_AT_YIELD:%i]\n", name.c_str(), mat->strength.strain_at_yield[strain]); + out.print("\t[{}_STRAIN_AT_YIELD:{}]\n", name, mat->strength.strain_at_yield[strain]); } if (mat->strength.max_edge != 0) - out.print("\t[MAX_EDGE:%i]\n", mat->strength.max_edge); + out.print("\t[MAX_EDGE:{}]\n", mat->strength.max_edge); if (mat->strength.absorption != 0) - out.print("\t[ABSORPTION:%i]\n", mat->strength.absorption); + out.print("\t[ABSORPTION:{}]\n", mat->strength.absorption); FOR_ENUM_ITEMS(material_flags, i) { if (mat->flags.is_set(i)) - out.print("\t[%s]\n", ENUM_KEY_STR(material_flags, i).c_str()); + out.print("\t[{}]\n", ENUM_KEY_STR(material_flags, i)); } if (mat->extract_storage != item_type::BARREL) - out.print("\t[EXTRACT_STORAGE:%s]\n", ENUM_KEY_STR(item_type, mat->extract_storage).c_str()); + out.print("\t[EXTRACT_STORAGE:{}]\n", ENUM_KEY_STR(item_type, mat->extract_storage)); if (mat->butcher_special_type != item_type::NONE || mat->butcher_special_subtype != -1) - out.print("\t[BUTCHER_SPECIAL:%s:%s]\n", ENUM_KEY_STR(item_type, mat->butcher_special_type).c_str(), (mat->butcher_special_subtype == -1) ? "NONE" : "?"); + out.print("\t[BUTCHER_SPECIAL:{}:{}]\n", ENUM_KEY_STR(item_type, mat->butcher_special_type), (mat->butcher_special_subtype == -1) ? "NONE" : "?"); if (mat->meat_name[0].size() || mat->meat_name[1].size() || mat->meat_name[2].size()) - out.print("\t[MEAT_NAME:%s:%s:%s]\n", mat->meat_name[0].c_str(), mat->meat_name[1].c_str(), mat->meat_name[2].c_str()); + out.print("\t[MEAT_NAME:{}:{}:{}]\n", mat->meat_name[0], mat->meat_name[1], mat->meat_name[2]); if (mat->block_name[0].size() || mat->block_name[1].size()) - out.print("\t[BLOCK_NAME:%s:%s]\n", mat->block_name[0].c_str(), mat->block_name[1].c_str()); + out.print("\t[BLOCK_NAME:{}:{}]\n", mat->block_name[0], mat->block_name[1]); for (std::string *s : mat->reaction_class) - out.print("\t[REACTION_CLASS:%s]\n", s->c_str()); + out.print("\t[REACTION_CLASS:{}]\n", *s); for (size_t i = 0; i < mat->reaction_product.id.size(); i++) { if ((*mat->reaction_product.str[0][i] == "NONE") && (*mat->reaction_product.str[1][i] == "NONE")) - out.print("\t[MATERIAL_REACTION_PRODUCT:%s:%s:%s%s%s]\n", mat->reaction_product.id[i]->c_str(), mat->reaction_product.str[2][i]->c_str(), mat->reaction_product.str[3][i]->c_str(), mat->reaction_product.str[4][i]->size() ? ":" : "", mat->reaction_product.str[4][i]->c_str()); + out.print("\t[MATERIAL_REACTION_PRODUCT:{}:{}:{}{}{}]\n", *mat->reaction_product.id[i], *mat->reaction_product.str[2][i], *mat->reaction_product.str[3][i], mat->reaction_product.str[4][i]->size() ? ":" : "", *mat->reaction_product.str[4][i]); else - out.print("\t[ITEM_REACTION_PRODUCT:%s:%s:%s:%s:%s%s%s]\n", mat->reaction_product.id[i]->c_str(), mat->reaction_product.str[0][i]->c_str(), mat->reaction_product.str[1][i]->c_str(), mat->reaction_product.str[2][i]->c_str(), mat->reaction_product.str[3][i]->c_str(), mat->reaction_product.str[4][i]->size() ? ":" : "", mat->reaction_product.str[4][i]->c_str()); + out.print("\t[ITEM_REACTION_PRODUCT:{}:{}:{}:{}:{}{}]\n", *mat->reaction_product.id[i], *mat->reaction_product.str[0][i], *mat->reaction_product.str[1][i], *mat->reaction_product.str[2][i], *mat->reaction_product.str[3][i], *mat->reaction_product.str[4][i]); } if (mat->hardens_with_water.mat_type != -1) - out.print("\t[HARDENS_WITH_WATER:%s:%s%s%s]\n", mat->hardens_with_water.str[0].c_str(), mat->hardens_with_water.str[1].c_str(), mat->hardens_with_water.str[2].size() ? ":" : "", mat->hardens_with_water.str[2].c_str()); + out.print("\t[HARDENS_WITH_WATER:{}:{}{}{}]\n", mat->hardens_with_water.str[0], mat->hardens_with_water.str[1], mat->hardens_with_water.str[2].size() ? ":" : "", mat->hardens_with_water.str[2]); if (mat->powder_dye != -1) - out.print("\t[POWDER_DYE:%s]\n", world->raws.descriptors.colors[mat->powder_dye]->id.c_str()); + out.print("\t[POWDER_DYE:{}]\n", world->raws.descriptors.colors[mat->powder_dye]->id); if (mat->soap_level != -0) - out.print("\t[SOAP_LEVEL:%o]\n", mat->soap_level); + out.print("\t[SOAP_LEVEL:{}]\n", mat->soap_level); for (size_t i = 0; i < mat->syndrome.syndrome.size(); i++) out.print("\t[SYNDROME] ...\n"); diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index c0944249f1..3b0bd860d4 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -109,7 +109,7 @@ command_result eventExample(color_ostream& out, vector& parameters) { //static int timerCount=0; //static int timerDenom=0; void jobInitiated(color_ostream& out, void* job_) { - out.print("Job initiated! %p\n", job_); + out.print("Job initiated! {}\n", static_cast(job_)); /* df::job* job = (df::job*)job_; out.print(" completion_timer = %d\n", job->completion_timer); @@ -120,37 +120,37 @@ void jobInitiated(color_ostream& out, void* job_) { } void jobCompleted(color_ostream& out, void* job) { - out.print("Job completed! %p\n", job); + out.print("Job completed! {}\n", static_cast(job)); } void timePassed(color_ostream& out, void* ptr) { - out.print("Time: %zi\n", (intptr_t)(ptr)); + out.print("Time: {}\n", (intptr_t)(ptr)); } void unitDeath(color_ostream& out, void* ptr) { - out.print("Death: %zi\n", (intptr_t)(ptr)); + out.print("Death: {}\n", (intptr_t)(ptr)); } void itemCreate(color_ostream& out, void* ptr) { int32_t item_index = df::item::binsearch_index(df::global::world->items.all, (intptr_t)ptr); if ( item_index == -1 ) { - out.print("%s, %d: Error.\n", __FILE__, __LINE__); + out.print("{}: Error.\n", __FILE__, __LINE__); } df::item* item = df::global::world->items.all[item_index]; df::item_type type = item->getType(); df::coord pos = item->pos; - out.print("Item created: %zi, %s, at (%d,%d,%d)\n", (intptr_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z); + out.print("Item created: {}, {}, at ({},{},{})\n", (intptr_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z); } void building(color_ostream& out, void* ptr) { - out.print("Building created/destroyed: %zi\n", (intptr_t)ptr); + out.print("Building created/destroyed: {}\n", (intptr_t)ptr); } void construction(color_ostream& out, void* ptr) { - out.print("Construction created/destroyed: %p\n", ptr); + out.print("Construction created/destroyed: {}\n", ptr); df::construction* constr = (df::construction*)ptr; df::coord pos = constr->pos; - out.print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z); + out.print(" ({},{},{})\n", pos.x, pos.y, pos.z); if ( df::construction::find(pos) == NULL ) out.print(" construction destroyed\n"); else @@ -160,19 +160,19 @@ void construction(color_ostream& out, void* ptr) { void syndrome(color_ostream& out, void* ptr) { EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr; - out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex); + out.print("Syndrome started: unit {}, syndrome {}.\n", data->unitId, data->syndromeIndex); } void invasion(color_ostream& out, void* ptr) { - out.print("New invasion! %zi\n", (intptr_t)ptr); + out.print("New invasion! {}\n", (intptr_t)ptr); } void unitAttack(color_ostream& out, void* ptr) { EventManager::UnitAttackData* data = (EventManager::UnitAttackData*)ptr; - out.print("unit %d attacks unit %d\n", data->attacker, data->defender); + out.print("unit {} attacks unit {}\n", data->attacker, data->defender); df::unit* defender = df::unit::find(data->defender); if (!defender) { - out.printerr("defender %d does not exist\n", data->defender); + out.printerr("defender {} does not exist\n", data->defender); return; } int32_t woundIndex = df::unit_wound::binsearch_index(defender->body.wounds, data->wound); @@ -187,6 +187,6 @@ void unitAttack(color_ostream& out, void* ptr) { for ( auto a = parts.begin(); a != parts.end(); a++ ) { int32_t body_part_id = (*a); df::body_part_raw* part = defender->body.body_plan->body_parts[body_part_id]; - out.print(" %s\n", part->name_singular[0]->c_str()); + out.print(" {}\n", *part->name_singular[0]); } } diff --git a/plugins/devel/frozen.cpp b/plugins/devel/frozen.cpp index 6c4264783e..a6d862a5a5 100644 --- a/plugins/devel/frozen.cpp +++ b/plugins/devel/frozen.cpp @@ -55,7 +55,7 @@ command_result df_frozenlava (color_ostream &out, vector & parameters) int tiles = changeLiquid(tile_liquid::Magma); if (tiles) - out.print("Changed %i tiles of ice into frozen lava.\n", tiles); + out.print("Changed {} tiles of ice into frozen lava.\n", tiles); return CR_OK; } @@ -72,7 +72,7 @@ command_result df_frozenwater (color_ostream &out, vector & parameters) int tiles = changeLiquid(tile_liquid::Water); if (tiles) - out.print("Changed %i tiles of ice into frozen water.\n", tiles); + out.print("Changed {} tiles of ice into frozen water.\n", tiles); return CR_OK; } diff --git a/plugins/devel/kittens.cpp b/plugins/devel/kittens.cpp index 69430e69da..d9dbf27015 100644 --- a/plugins/devel/kittens.cpp +++ b/plugins/devel/kittens.cpp @@ -115,14 +115,14 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) uint64_t time2 = GetTimeMs64(); uint64_t delta = time2-timeLast; timeLast = time2; - out.print("Time delta = %d ms\n", int(delta)); + out.print("Time delta = {} ms\n", int(delta)); } if(trackmenu_flg) { if (last_menu != plotinfo->main.mode) { last_menu = plotinfo->main.mode; - out.print("Menu: %d\n",last_menu); + out.print("Menu: {}\n", ENUM_AS_STR(last_menu)); } } if(trackpos_flg) @@ -134,14 +134,14 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) last_designation[0] = desig_x; last_designation[1] = desig_y; last_designation[2] = desig_z; - out.print("Designation: %d %d %d\n",desig_x, desig_y, desig_z); + out.print("Designation: {} {} {}\n", desig_x, desig_y, desig_z); } df::coord mousePos = Gui::getMousePos(); if(mousePos.x != last_mouse[0] || mousePos.y != last_mouse[1]) { last_mouse[0] = mousePos.x; last_mouse[1] = mousePos.y; - out.print("Mouse: %d %d\n",mousePos.x, mousePos.y); + out.print("Mouse: {} {}\n", mousePos.x, mousePos.y); } } return CR_OK; @@ -158,7 +158,7 @@ command_result trackmenu (color_ostream &out, vector & parameters) { is_enabled = true; last_menu = plotinfo->main.mode; - out.print("Menu: %d\n",last_menu); + out.print("Menu: {}\n", ENUM_AS_STR(last_menu)); trackmenu_flg = true; return CR_OK; } @@ -182,10 +182,10 @@ command_result colormods (color_ostream &out, vector & parameters) for(df::creature_raw* rawlion : vec) { df::caste_raw * caste = rawlion->caste[0]; - out.print("%s\nCaste addr %p\n",rawlion->creature_id.c_str(), &caste->color_modifiers); + out.print("{}\nCaste addr {}\n",rawlion->creature_id, static_cast(&caste->color_modifiers)); for(size_t j = 0; j < caste->color_modifiers.size();j++) { - out.print("mod %zd: %p\n", j, caste->color_modifiers[j]); + out.print("mod {}: {}\n", j, static_cast(caste->color_modifiers[j])); } } return CR_OK; @@ -203,7 +203,7 @@ command_result ktimer (color_ostream &out, vector & parameters) uint64_t timeend = GetTimeMs64(); timeLast = timeend; timering = true; - out.print("Time to suspend = %d ms\n", int(timeend - timestart)); + out.print("Time to suspend = {} ms\n", int(timeend - timestart)); } is_enabled = true; return CR_OK; @@ -300,9 +300,9 @@ struct Connected : public ClearMem { return this; } ~Connected() { - INFO(command,*out).print("Connected %d had %d count. " - "It was caller %d times. " - "It was callee %d times.\n", + INFO(command,*out).print("Connected {} had {} count. " + "It was caller {} times. " + "It was callee {} times.\n", id, count, caller, callee.load()); } }; diff --git a/plugins/devel/memutils.cpp b/plugins/devel/memutils.cpp index c2a7387fdd..b7ce98ea73 100644 --- a/plugins/devel/memutils.cpp +++ b/plugins/devel/memutils.cpp @@ -52,7 +52,7 @@ namespace memutils { lua_pushstring(state, expr); if (!Lua::SafeCall(*out, state, 1, 1)) { - out->printerr("Failed to evaluate %s\n", expr); + out->printerr("Failed to evaluate {}\n", expr); return NULL; } @@ -60,7 +60,7 @@ namespace memutils { lua_swap(state); if (!Lua::SafeCall(*out, state, 1, 1) || !lua_isinteger(state, -1)) { - out->printerr("Failed to get address: %s\n", expr); + out->printerr("Failed to get address: {}\n", expr); return NULL; } diff --git a/plugins/devel/memview.cpp b/plugins/devel/memview.cpp index a9fba897e1..01397ef7d2 100644 --- a/plugins/devel/memview.cpp +++ b/plugins/devel/memview.cpp @@ -75,7 +75,7 @@ void outputHex(uint8_t *buf,uint8_t *lbuf,size_t len,size_t start,color_ostream for(size_t i=0;i8X} ",i+start); for(size_t j=0;(j2X}",static_cast(buf[j+i])); //if modfied show a star else - con.print(" %02X",buf[j+i]); + con.print(" {:0>2X}",static_cast(buf[j+i])); } con.reset_color(); con.print(" | "); for(size_t j=0;(j31)&&(buf[j+i]<128)) //only printable ascii - con.print("%c",buf[j+i]); + con.print("{}",static_cast(buf[j+i])); else con.print("."); con.print("\n"); @@ -187,7 +187,7 @@ command_result memview (color_ostream &out, vector & parameters) isValid=true; if(!isValid) { - out.printerr("Invalid address: %p\n",memdata.addr); + out.printerr("Invalid address: {}\n",memdata.addr); mymutex->unlock(); return CR_OK; } diff --git a/plugins/devel/onceExample.cpp b/plugins/devel/onceExample.cpp index ebc03e32b7..73361c53e7 100644 --- a/plugins/devel/onceExample.cpp +++ b/plugins/devel/onceExample.cpp @@ -25,7 +25,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & parameters) { - out.print("Already done = %d.\n", DFHack::Once::alreadyDone("onceExample_1")); + out.print("Already done = {}.\n", DFHack::Once::alreadyDone("onceExample_1")); if ( DFHack::Once::doOnce("onceExample_1") ) { out.print("Printing this message once!\n"); } diff --git a/plugins/devel/stepBetween.cpp b/plugins/devel/stepBetween.cpp index 0eca4ea8c9..9acbce904b 100644 --- a/plugins/devel/stepBetween.cpp +++ b/plugins/devel/stepBetween.cpp @@ -79,7 +79,7 @@ df::coord prev; command_result stepBetween (color_ostream &out, std::vector & parameters) { df::coord bob = Gui::getCursorPos(); - out.print("(%d,%d,%d), (%d,%d,%d): canStepBetween = %d, canWalkBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canStepBetween(prev, bob), Maps::canWalkBetween(prev,bob)); + out.print("({},{},{}), ({},{},{}): canStepBetween = {}, canWalkBetween = {}\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canStepBetween(prev, bob), Maps::canWalkBetween(prev,bob)); prev = bob; return CR_OK; diff --git a/plugins/devel/tilesieve.cpp b/plugins/devel/tilesieve.cpp index 6b36b8dd0d..9f21d98368 100644 --- a/plugins/devel/tilesieve.cpp +++ b/plugins/devel/tilesieve.cpp @@ -73,7 +73,7 @@ command_result tilesieve(color_ostream &out, std::vector & params) if(seen.count(tt)) continue; seen.insert(tt); - out.print("Found tile %x @ %d %d %d\n", tt, block->map_pos.x + x, block->map_pos.y + y, block->map_pos.z); + out.print("Found tile {} @ {} {} {}\n", ENUM_AS_STR(tt), block->map_pos.x + x, block->map_pos.y + y, block->map_pos.z); } } return CR_OK; diff --git a/plugins/devel/vectors.cpp b/plugins/devel/vectors.cpp index 279fe8a0d5..ac72de3074 100644 --- a/plugins/devel/vectors.cpp +++ b/plugins/devel/vectors.cpp @@ -134,7 +134,7 @@ static void printVec(color_ostream &con, const char* msg, t_vecTriplet *vec, uintptr_t length = (intptr_t)vec->end - (intptr_t)vec->start; uintptr_t offset = pos - start; - con.print("%8s offset 0x%06zx, addr 0x%01zx, start 0x%01zx, length %zi", + con.print("{:8} offset 0x{:06x}, addr 0x{:01x}, start 0x{:01x}, length {:}", msg, offset, pos, intptr_t(vec->start), length); if (length >= 4 && length % 4 == 0) { @@ -146,7 +146,7 @@ static void printVec(color_ostream &con, const char* msg, t_vecTriplet *vec, } std::string classname; if (Core::getInstance().vinfo->getVTableName(ptr, classname)) - con.print(", 1st item: %s", classname.c_str()); + con.print(", 1st item: {}", classname); } con.print("\n"); } @@ -197,10 +197,10 @@ command_result df_vectors (color_ostream &con, vector & parameters) // Found the range containing the start if (!range.isInRange((void *)end)) { - con.print("Scanning %zi bytes would read past end of memory " + con.print("Scanning {} bytes would read past end of memory " "range.\n", bytes); size_t diff = end - (intptr_t)range.end; - con.print("Cutting bytes down by %zi.\n", diff); + con.print("Cutting bytes down by {}.\n", diff); end = (uintptr_t) range.end; } diff --git a/plugins/devel/zoom.cpp b/plugins/devel/zoom.cpp index 18e8d62e32..d37ed3418a 100644 --- a/plugins/devel/zoom.cpp +++ b/plugins/devel/zoom.cpp @@ -42,7 +42,7 @@ command_result df_zoom (color_ostream &out, std::vector & paramete return CR_WRONG_USAGE; if (zcmap.find(parameters[0]) == zcmap.end()) { - out.printerr("Unrecognized zoom command: %s\n", parameters[0].c_str()); + out.printerr("Unrecognized zoom command: {}\n", parameters[0]); out.print("Valid commands:"); for (auto it = zcmap.begin(); it != zcmap.end(); ++it) { diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 48984e1b9f..259978f9bf 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -48,9 +48,6 @@ namespace DFHack { DBG_DECLARE(dignow, channels, DebugCategory::LINFO); } -#define COORD "%" PRIi16 " %" PRIi16 " %" PRIi16 -#define COORDARGS(id) id.x, id.y, id.z - using namespace DFHack; struct designation{ @@ -486,7 +483,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, DFCoord pos_below(pos.x, pos.y, pos.z-1); if (can_dig_channel(tt) && map.ensureBlockAt(pos_below) && is_diggable(map, pos_below, map.tiletypeAt(pos_below))) { - TRACE(channels).print("dig_tile: channeling at (" COORD ") [can_dig_channel: true]\n",COORDARGS(pos_below)); + TRACE(channels).print("dig_tile: channeling at ({}) [can_dig_channel: true]\n", pos_below); target_type = df::tiletype::OpenSpace; DFCoord pos_above(pos.x, pos.y, pos.z+1); if (map.ensureBlockAt(pos_above)) { @@ -503,7 +500,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, return true; } } else { - DEBUG(channels).print("dig_tile: failed to channel at (" COORD ") [can_dig_channel: false]\n", COORDARGS(pos_below)); + DEBUG(channels).print("dig_tile: failed to channel at ({}) [can_dig_channel: false]\n", pos_below); } break; } @@ -550,8 +547,8 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, case df::tile_dig_designation::No: default: out.printerr( - "unhandled dig designation for tile (%d, %d, %d): %d\n", - pos.x, pos.y, pos.z, designation); + "unhandled dig designation for tile ({}, {}, {}): {}\n", + pos.x, pos.y, pos.z, ENUM_AS_STR(designation)); } // fail if unhandled or no change to tile @@ -559,7 +556,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, return false; dug_tiles.emplace_back(map, pos); - TRACE(general).print("dig_tile: digging the designation tile at (" COORD ")\n",COORDARGS(pos)); + TRACE(general).print("dig_tile: digging the designation tile at ({})\n",pos); dig_type(map, pos, target_type); clean_ramps(map, pos); @@ -898,10 +895,8 @@ static void create_boulders(color_ostream &out, if (num_items != coords.size()) { MaterialInfo material; material.decode(prod->mat_type, prod->mat_index); - out.printerr("unexpected number of %s %s produced: expected %zd," - " got %zd.\n", - material.toString().c_str(), - ENUM_KEY_STR(item_type, prod->item_type).c_str(), + out.printerr("unexpected number of {} {} produced: expected {}, got {}.\n", + material.toString(), ENUM_KEY_STR(item_type, prod->item_type), coords.size(), num_items); num_items = std::min(num_items, entry.second.size()); } @@ -911,7 +906,7 @@ static void create_boulders(color_ostream &out, dump_pos : simulate_fall(coords[i]); if (!Maps::ensureTileBlock(pos)) { out.printerr( - "unable to place boulder generated at (%d, %d, %d)\n", + "unable to place boulder generated at ({}, {}, {})\n", coords[i].x, coords[i].y, coords[i].z); continue; } @@ -956,7 +951,7 @@ static void post_process_dug_tiles(color_ostream &out, continue; if (!Maps::ensureTileBlock(resting_pos)) { - out.printerr("No valid tile beneath (%d, %d, %d); can't move" + out.printerr("No valid tile beneath ({},{},{}) can't move" " units and items to floor", pos.x, pos.y, pos.z); continue; diff --git a/plugins/dig.cpp b/plugins/dig.cpp index 8bdcca59d6..5fbf23b251 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -68,7 +68,7 @@ static bool is_painting_warm = false; static bool is_painting_damp = false; DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - textures = Textures::loadTileset("hack/data/art/damp_dig_map.png", 32, 32, true); + textures = Textures::loadTileset(Core::getInstance().getHackPath() / "data" / "art" / "damp_dig_map.png", 32, 32, true); commands.push_back(PluginCommand( "digv", @@ -108,7 +108,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector getassignment(pos), is_warm(pos)); if (warm_mask->getassignment(pos) && is_warm(pos)) { - DEBUG(log,out).print("revealing warm dig tile at (%d,%d,%d)\n", pos.x, pos.y, pos.z); + DEBUG(log,out).print("revealing warm dig tile at ({},{},{})\n", pos.x, pos.y, pos.z); block->designation[pos.x&15][pos.y&15].bits.hidden = false; } } if (auto damp_mask = World::getPersistentTilemask(damp_config, block)) { - TRACE(log,out).print("testing tile at (%d,%d,%d); mask:%d, damp:%d\n", pos.x, pos.y, pos.z, + TRACE(log,out).print("testing tile at ({},{},{}); mask:{}, damp:{}\n", pos.x, pos.y, pos.z, damp_mask->getassignment(pos), is_damp(pos)); if (damp_mask->getassignment(pos) && is_damp(pos)) { - DEBUG(log,out).print("revealing damp dig tile at (%d,%d,%d)\n", pos.x, pos.y, pos.z); + DEBUG(log,out).print("revealing damp dig tile at ({},{},{})\n", pos.x, pos.y, pos.z); block->designation[pos.x&15][pos.y&15].bits.hidden = false; } } @@ -418,7 +418,7 @@ static void unhide_surrounding_tagged_tiles(color_ostream& out, void* job_ptr) { return; const auto & pos = job->pos; - TRACE(log,out).print("handing dig job at (%d,%d,%d)\n", pos.x, pos.y, pos.z); + TRACE(log,out).print("handing dig job at ({},{},{})\n", pos.x, pos.y, pos.z); process_taken_dig_job(out, pos); @@ -626,7 +626,7 @@ int32_t parse_priority(color_ostream &out, vector ¶meters) } else { - out.printerr("invalid priority specified; reverting to %i\n", default_priority); + out.printerr("invalid priority specified; reverting to {}\n", default_priority); break; } } @@ -1470,7 +1470,7 @@ command_result digv (color_ostream &out, vector & parameters) con.printerr("This tile is not a vein.\n"); 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("{} tiletype: {}, veinmat: {}, designation: 0x{:x} ... DIGGING!\n", xy, ENUM_AS_STR(tt), veinmat, des.whole); stack flood; flood.push(xy); @@ -1654,7 +1654,7 @@ command_result digl (color_ostream &out, vector & parameters) con.printerr("This is a vein. Use digv instead!\n"); 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("{}/{}/{}/ tiletype: {}, basemat: {}, designation: 0x{:x} ... DIGGING!\n", cx,cy,cz, ENUM_AS_STR(tt), basemat, des.whole); stack flood; flood.push(xy); @@ -1841,7 +1841,7 @@ command_result digtype (color_ostream &out, vector & parameters) automine = false; else { - out.printerr("Invalid parameter: '%s'.\n", parameter.c_str()); + out.printerr("Invalid parameter: '{}'.\n", parameter); return CR_FAILURE; } } @@ -1873,7 +1873,7 @@ command_result digtype (color_ostream &out, vector & parameters) out.printerr("This tile is not a vein.\n"); 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("({},{},{}) tiletype: {}, veinmat: {}, designation: 0x{:x} ... DIGGING!\n", cx,cy,cz, ENUM_AS_STR(tt), veinmat, baseDes.whole); if ( targetDigType != -1 ) { @@ -1910,7 +1910,7 @@ command_result digtype (color_ostream &out, vector & parameters) //designate it for digging if ( !mCache->testCoord(current) ) { - out.printerr("testCoord failed at (%d,%d,%d)\n", x, y, z); + out.printerr("testCoord failed at ({},{},{})\n", x, y, z); return CR_FAILURE; } @@ -2158,7 +2158,7 @@ static bool blink(int delay) { } static void paintScreenWarmDamp(bool aquifer_mode = false, bool show_damp = false) { - TRACE(log).print("entering paintScreenDampWarm aquifer_mode=%d, show_damp=%d\n", aquifer_mode, show_damp); + TRACE(log).print("entering paintScreenDampWarm aquifer_mode={}, show_damp={}\n", aquifer_mode, show_damp); static Screen::Pen empty_pen; @@ -2220,7 +2220,7 @@ static void paintScreenWarmDamp(bool aquifer_mode = false, bool show_damp = fals bump_layers(*pen, x, y); } } else { - TRACE(log).print("scanning map tile at (%d, %d, %d) screen offset (%d, %d)\n", + TRACE(log).print("scanning map tile at ({},{},{}) screen offset ({},{})\n", pos.x, pos.y, pos.z, x, y); auto des = Maps::getTileDesignation(pos); @@ -2231,7 +2231,7 @@ static void paintScreenWarmDamp(bool aquifer_mode = false, bool show_damp = fals Screen::Pen cur_tile = Screen::readTile(x, y, true); if (!cur_tile.valid()) { - DEBUG(log).print("cannot read tile at offset %d, %d\n", x, y); + DEBUG(log).print("cannot read tile at offset {}, {}\n", x, y); continue; } @@ -2465,7 +2465,7 @@ static void paintScreenDesignated() { if (!Maps::isValidTilePos(map_pos)) continue; - TRACE(log).print("scanning map tile at (%d, %d, %d) screen offset (%d, %d)\n", + TRACE(log).print("scanning map tile at ({},{},{}) screen offset ({},{})\n", map_pos.x, map_pos.y, map_pos.z, x, y); Screen::Pen cur_tile; diff --git a/plugins/digFlood.cpp b/plugins/digFlood.cpp index 045e6c61d6..c1d0eccbc5 100644 --- a/plugins/digFlood.cpp +++ b/plugins/digFlood.cpp @@ -181,7 +181,7 @@ command_result digFlood (color_ostream &out, std::vector & paramet } } - out.print("Could not find material \"%s\".\n", parameters[a].c_str()); + out.print("Could not find material \"{}\".\n", parameters[a]); return CR_WRONG_USAGE; loop: continue; diff --git a/plugins/diggingInvaders/assignJob.cpp b/plugins/diggingInvaders/assignJob.cpp index eb74433b86..1bd5c97e4d 100644 --- a/plugins/diggingInvaders/assignJob.cpp +++ b/plugins/diggingInvaders/assignJob.cpp @@ -231,13 +231,13 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_mapmat_type = material.type; @@ -255,7 +255,7 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_mapsite_id), NULL); if ( out_items.size() != 1 ) { - out.print("%s, %d: wrong size: %zu.\n", __FILE__, __LINE__, out_items.size()); + out.print("{}, {}: wrong size: {}.\n", __FILE__, __LINE__, out_items.size()); return -1; } out_items[0]->moveToGround(firstInvader->pos.x, firstInvader->pos.y, firstInvader->pos.z); diff --git a/plugins/diggingInvaders/diggingInvaders.cpp b/plugins/diggingInvaders/diggingInvaders.cpp index d883ef4dba..fff7f033ea 100644 --- a/plugins/diggingInvaders/diggingInvaders.cpp +++ b/plugins/diggingInvaders/diggingInvaders.cpp @@ -276,13 +276,13 @@ command_result diggingInvadersCommand(color_ostream& out, std::vectorjob.current_job && lastDigger->job.current_job->id == lastInvasionJob ) { return; } - //out.print("%s,%d: lastDigger = %d, last job = %d, last digger's job = %d\n", __FILE__, __LINE__, lastInvasionDigger, lastInvasionJob, !lastDigger ? -1 : (!lastDigger->job.current_job ? -1 : lastDigger->job.current_job->id)); + //out.print("{},{}: lastDigger = {}, last job = {}, last digger's job = {}\n", __FILE__, __LINE__, lastInvasionDigger, lastInvasionJob, !lastDigger ? -1 : (!lastDigger->job.current_job ? -1 : lastDigger->job.current_job->id)); lastInvasionDigger = lastInvasionJob = -1; clearDijkstra(); @@ -382,7 +382,7 @@ void findAndAssignInvasionJob(color_ostream& out, void* tickTime) { } else if ( unit->flags1.bits.active_invader ) { df::creature_raw* raw = df::creature_raw::find(unit->race); if ( raw == NULL ) { - out.print("%s,%d: WTF? Couldn't find creature raw.\n", __FILE__, __LINE__); + out.print("{},{}: WTF? Couldn't find creature raw.\n", __FILE__, __LINE__); continue; } /* @@ -464,7 +464,7 @@ void findAndAssignInvasionJob(color_ostream& out, void* tickTime) { fringe.erase(fringe.begin()); //out.print("line %d: fringe size = %d, localPtsFound = %d / %d, closedSetSize = %d, pt = %d,%d,%d\n", __LINE__, fringe.size(), localPtsFound, localPts.size(), closedSet.size(), pt.x,pt.y,pt.z); if ( closedSet.find(pt) != closedSet.end() ) { - out.print("%s, line %d: Double closure! Bad!\n", __FILE__, __LINE__); + out.print("{},{}: Double closure! Bad!\n", __FILE__, __LINE__); break; } closedSet.insert(pt); @@ -511,7 +511,7 @@ void findAndAssignInvasionJob(color_ostream& out, void* tickTime) { delete myEdges; } // clock_t time = clock() - t0; - //out.print("tickTime = %d, time = %d, totalEdgeTime = %d, total points = %d, total edges = %d, time per point = %.3f, time per edge = %.3f, clocks/sec = %d\n", (int32_t)tickTime, time, totalEdgeTime, closedSet.size(), edgeCount, (float)time / closedSet.size(), (float)time / edgeCount, CLOCKS_PER_SEC); + //out.print("tickTime = {}, time = {}, totalEdgeTime = {}, total points = {}, total edges = {}, time per point = {:.3f}, time per edge = {:.3f}, clocks/sec = {}\n", (int32_t)tickTime, time, totalEdgeTime, closedSet.size(), edgeCount, (float)time / closedSet.size(), (float)time / edgeCount, CLOCKS_PER_SEC); fringe.clear(); if ( !foundTarget ) @@ -590,7 +590,7 @@ void findAndAssignInvasionJob(color_ostream& out, void* tickTime) { //cancel it job->flags.bits.item_lost = 1; - out.print("%s,%d: cancelling job %d.\n", __FILE__,__LINE__, job->id); + out.print("{},{}: cancelling job {}.\n", __FILE__,__LINE__, job->id); //invaderJobs.remove(job->id); } invaderJobs.erase(lastInvasionJob); diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index 6f3e32d00f..7847dfa8e8 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -69,17 +69,17 @@ DFhackCExport command_result plugin_init(color_ostream &out, vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -140,7 +140,7 @@ static void dwarfvet_cycle(color_ostream &out) { // mark that we have recently run cycle_timestamp = world->frame_counter; - DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + DEBUG(cycle,out).print("running {} cycle\n", plugin_name); Lua::CallLuaModuleFunction(out, "plugins.dwarfvet", "checkup"); } diff --git a/plugins/edgescroll.cpp b/plugins/edgescroll.cpp new file mode 100644 index 0000000000..cdc0c349d2 --- /dev/null +++ b/plugins/edgescroll.cpp @@ -0,0 +1,232 @@ +#include "ColorText.h" +#include "MemAccess.h" +#include "PluginManager.h" + +#include "modules/Gui.h" +#include "modules/DFSDL.h" + +#include "df/enabler.h" +#include "df/gamest.h" +#include "df/graphic.h" +#include "df/graphic_viewportst.h" +#include "df/renderer_2d.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/viewscreen_worldst.h" +#include "df/viewscreen_new_regionst.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_generatorst.h" + +#include +#include + +using namespace DFHack; + +DFHACK_PLUGIN("edgescroll"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(enabler); +REQUIRE_GLOBAL(game); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(gps); + +// Cooldown between edge scroll actions +constexpr uint32_t cooldown_ms = 100; +// Number of pixels from border to trigger edgescroll +constexpr int border_range = 5; + +// Controls how much edge scroll moves +constexpr int map_scroll_pixels = 100; +constexpr int world_scroll_tiles = 3; +constexpr int world_scroll_tiles_zoomed = 6; + +DFhackCExport command_result plugin_init([[maybe_unused]]color_ostream &out, [[maybe_unused]] std::vector &commands) { + return CR_OK; +} + +DFhackCExport command_result plugin_enable([[maybe_unused]]color_ostream &out, bool enable) { + is_enabled = enable; + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown([[maybe_unused]] color_ostream &out) { + return CR_OK; +} + +static std::atomic_bool callback_queued = false; + +struct scroll_state { + int8_t xdiff = 0; + int8_t ydiff = 0; +}; + +static scroll_state state; +static scroll_state queued; + +static void render_thread_cb() { + queued = {}; + // Ignore the mouse if outside the window + if (!enabler->mouse_focus) { + callback_queued.store(false); + return; + } + + // Calculate the render rect in window coordinates + auto* renderer = virtual_cast(enabler->renderer); + int origin_x, origin_y; + int end_x, end_y; + DFSDL::DFSDL_RenderLogicalToWindow( + (SDL_Renderer*)renderer->sdl_renderer, (float)renderer->origin_x, + (float)renderer->origin_y, &origin_x, &origin_y); + DFSDL::DFSDL_RenderLogicalToWindow( + (SDL_Renderer*)renderer->sdl_renderer, + (float)renderer->cur_w - (float)renderer->origin_x, + (float)(renderer->cur_h - renderer->origin_y), &end_x, &end_y); + + // Get the mouse location in window coordinates + int mx, my; + DFSDL::DFSDL_GetMouseState(&mx, &my); + + if (mx <= origin_x + border_range) { + queued.xdiff--; + } else if (mx >= end_x - border_range) { + queued.xdiff++; + } + if (my <= origin_y + border_range) { + queued.ydiff--; + } else if (my >= end_y - border_range) { + queued.ydiff++; + } + + callback_queued.store(false); +} + +static bool update_mouse_pos() { + if (callback_queued.load()) + return false; // Queued callback not complete, check back later + + // Queued callback complete, save the results and enqueue again + state = queued; + queued = {}; + DFHack::runOnRenderThread(render_thread_cb); + callback_queued.store(true); + return true; +} + +// Apply scroll whilst maintaining boundaries +template +static void apply_scroll(T* out, T diff, T min, T max) { + *out = std::min(std::max(*out + diff, min), max); +} + +// Scroll main fortress/adventure world views +static void scroll_dwarfmode(int xdiff, int ydiff) { + using df::global::window_x; + using df::global::window_y; + using df::global::game; + // Scale the movement by pixels, to keep scroll speeds visually consistent + int tilesize = gps->viewport_zoom_factor / 4; + int width = gps->main_viewport->dim_x; + int height = gps->main_viewport->dim_y; + + // Ensure the map doesn't go fully off-screen + int min_x = -width / 2; + int min_y = -height / 2; + int max_x = world->map.x_count - (width / 2); + int max_y = world->map.y_count - (height / 2); + apply_scroll(window_x, xdiff * std::max(1, map_scroll_pixels / tilesize), min_x, max_x); + apply_scroll(window_y, ydiff * std::max(1, map_scroll_pixels / tilesize), min_y, max_y); + + // Force a minimap update + game->minimap.update = 1; + game->minimap.mustmake = 1; +} + +template +static void scroll_world_internal(T* screen, int xdiff, int ydiff) { + if constexpr(std::is_same_v) { + if (screen->zoomed_in) { + int max_x = (world->world_data->world_width * 16)-1; + int max_y = (world->world_data->world_height * 16)-1; + apply_scroll(&screen->zoom_cent_x, xdiff * world_scroll_tiles_zoomed, 0, max_x); + apply_scroll(&screen->zoom_cent_y, ydiff * world_scroll_tiles_zoomed, 0, max_y); + return; + } + } + + int32_t *x, *y; + if constexpr(std::is_same_v) { + x = &world->worldgen_status.cursor_x; + y = &world->worldgen_status.cursor_y; + } else { + x = &screen->region_cent_x; + y = &screen->region_cent_y; + } + int max_x = world->world_data->world_width-1; + int max_y = world->world_data->world_height-1; + apply_scroll(x, xdiff * world_scroll_tiles, 0, max_x); + apply_scroll(y, ydiff * world_scroll_tiles, 0, max_y); +} + +template +struct overloads : Ts... { using Ts::operator()...; }; + +using world_map = std::variant; +static std::optional get_map() { + df::viewscreen* screen = Gui::getCurViewscreen(true); + screen = Gui::getDFViewscreen(true, screen); // Get the first non-dfhack viewscreen + if(!screen) + return {}; + + if (auto start_site = virtual_cast(screen)) + return start_site; + if (auto world_map = virtual_cast(screen)) + return world_map; + if (auto worldgen_map = virtual_cast(screen)) { + if (!world || world->worldgen_status.state <= df::world_generatorst::Initializing) + return {}; // Map isn't displayed yet + return worldgen_map; + } + return {}; +} + +static void scroll_world(world_map screen, int xdiff, int ydiff) { + const auto visitor = overloads { + [xdiff, ydiff](df::viewscreen_choose_start_sitest* s) {scroll_world_internal(s, xdiff, ydiff);}, + [xdiff, ydiff](df::viewscreen_worldst* s) {scroll_world_internal(s, xdiff, ydiff);}, + [xdiff, ydiff](df::viewscreen_new_regionst* s) {scroll_world_internal(s, xdiff, ydiff);}, + }; + std::visit(visitor, screen); +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + // Apply a cooldown to any potential edgescrolls + auto& core = Core::getInstance(); + static uint32_t last_action = 0; + uint32_t now = core.p->getTickCount(); + if (now < last_action + cooldown_ms) + return CR_OK; + + // Update mouse_x/y from values read in render thread callback + if (!update_mouse_pos()) + return CR_OK; // No new input to process + + if (state.xdiff == 0 && state.ydiff == 0) + return CR_OK; // No work to do + + // Ensure either a map viewscreen or the main viewport are visible + auto worldmap = get_map(); + if (!worldmap.has_value() && (!gps->main_viewport || !gps->main_viewport->flag.bits.active)) + return CR_OK; + + // Dispatch scrolling to active scrollables + if (worldmap.has_value()) + scroll_world(worldmap.value(), state.xdiff, state.ydiff); + else if (gps->main_viewport->flag.bits.active) + scroll_dwarfmode(state.xdiff, state.ydiff); + + // Update cooldown + last_action = now; + + return CR_OK; +} diff --git a/plugins/embark-assistant/embark-assistant.cpp b/plugins/embark-assistant/embark-assistant.cpp index db04308d0b..5b9d11f78a 100644 --- a/plugins/embark-assistant/embark-assistant.cpp +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -5,6 +5,7 @@ #include "PluginManager.h" #include "modules/Gui.h" +#include "modules/Hotkey.h" #include "modules/Screen.h" #include "../uicommon.h" @@ -161,7 +162,7 @@ struct start_site_hook : df::viewscreen_choose_start_sitest { { if (!embark_assist::main::state && input->count(interface_key::CUSTOM_A)) { - Core::getInstance().setHotkeyCmd("embark-assistant"); + Core::getInstance().getHotkeyManager()->setHotkeyCommand("embark-assistant"); return; } INTERPOSE_NEXT(feed)(input); diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp index bb90435343..8e695bd748 100644 --- a/plugins/embark-assistant/finder_ui.cpp +++ b/plugins/embark-assistant/finder_ui.cpp @@ -198,7 +198,7 @@ namespace embark_assist { size_t civ = 0; if (!infile) { - out.printerr("No profile file found at %s\n", profile_file_name); + out.printerr("No profile file found at {}\n", profile_file_name); return; } @@ -209,7 +209,7 @@ namespace embark_assist { while (true) { if (!fgets(line, count, infile) || line[0] != '[') { - out.printerr("Failed to find token start '[' at line %i\n", static_cast(i)); + out.printerr("Failed to find token start '[' at line {}\n", static_cast(i)); fclose(infile); return; } @@ -218,7 +218,7 @@ namespace embark_assist { if (line[k] == ':') { for (int l = 1; l < k; l++) { if (state->finder_list[static_cast(i) + civ].text.c_str()[l - 1] != line[l]) { - out.printerr("Token mismatch of %s vs %s\n", line, state->finder_list[static_cast(i) + civ].text.c_str()); + out.printerr("Token mismatch of {} vs {}\n", line, state->finder_list[static_cast(i) + civ].text.c_str()); fclose(infile); return; } @@ -242,7 +242,7 @@ namespace embark_assist { } if (!found) { - out.printerr("Value extraction failure from %s\n", line); + out.printerr("Value extraction failure from {}\n", line); fclose(infile); return; } @@ -252,7 +252,7 @@ namespace embark_assist { } if (!found) { - out.printerr("Value delimiter not found in %s\n", line); + out.printerr("Value delimiter not found in {}\n", line); fclose(infile); return; } diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index 9f3900f0c9..61da64959b 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -1450,21 +1450,21 @@ namespace embark_assist { case embark_assist::defs::evil_savagery_values::All: if (tile->savagery_count[i] < embark_size) { - if (trace) out.print("matcher::world_tile_match: Savagery All (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Savagery All ({}, {})\n", x, y); return false; } break; case embark_assist::defs::evil_savagery_values::Present: if (tile->savagery_count[i] == 0 && !tile->neighboring_savagery[i]) { - if (trace) out.print("matcher::world_tile_match: Savagery Present (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Savagery Present ({}, {})\n", x, y); return false; } break; case embark_assist::defs::evil_savagery_values::Absent: if (tile->savagery_count[i] > 256 - embark_size) { - if (trace) out.print("matcher::world_tile_match: Savagery Absent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Savagery Absent ({}, {})\n", x, y); return false; } break; @@ -1480,21 +1480,21 @@ namespace embark_assist { case embark_assist::defs::evil_savagery_values::All: if (tile->evilness_count[i] < embark_size) { - if (trace) out.print("matcher::world_tile_match: Evil All (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Evil All ({}, {})\n", x, y); return false; } break; case embark_assist::defs::evil_savagery_values::Present: if (tile->evilness_count[i] == 0 && !tile->neighboring_evilness[i]) { - if (trace) out.print("matcher::world_tile_match: Evil Present (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Evil Present ({}, {})\n", x, y); return false; } break; case embark_assist::defs::evil_savagery_values::Absent: if (tile->evilness_count[i] > 256 - embark_size) { - if (trace) out.print("matcher::world_tile_match: Evil Absent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Evil Absent ({}, {})\n", x, y); return false; } break; @@ -1511,14 +1511,14 @@ namespace embark_assist { case embark_assist::defs::aquifer_ranges::None: if (!(tile->aquifer & embark_assist::defs::None_Aquifer_Bit)) { - if (trace) out.print("matcher::world_tile_match: Aquifer None (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Aquifer None ({}, {})\n", x, y); return false; } break; case embark_assist::defs::aquifer_ranges::At_Most_Light: if (tile->aquifer == embark_assist::defs::Heavy_Aquifer_Bit) { - if (trace) out.print("matcher::world_tile_match: Aquifer At_Most_Light (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Aquifer At_Most_Light ({}, {})\n", x, y); return false; } break; @@ -1526,7 +1526,7 @@ namespace embark_assist { case embark_assist::defs::aquifer_ranges::None_Plus_Light: if (!(tile->aquifer & embark_assist::defs::None_Aquifer_Bit) || !(tile->aquifer & embark_assist::defs::Light_Aquifer_Bit)) { - if (trace) out.print("matcher::world_tile_match: Aquifer None_Plus_Light (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Aquifer None_Plus_Light ({}, {})\n", x, y); return false; } break; @@ -1534,21 +1534,21 @@ namespace embark_assist { case embark_assist::defs::aquifer_ranges::None_Plus_At_Least_Light: if (!(tile->aquifer & embark_assist::defs::None_Aquifer_Bit) || (tile->aquifer == embark_assist::defs::None_Aquifer_Bit)) { - if (trace) out.print("matcher::world_tile_match: Aquifer None_Plus_At_Least_Light (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Aquifer None_Plus_At_Least_Light ({}, {})\n", x, y); return false; } break; case embark_assist::defs::aquifer_ranges::Light: if (!(tile->aquifer & embark_assist::defs::Light_Aquifer_Bit)) { - if (trace) out.print("matcher::world_tile_match: Aquifer Light (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Aquifer Light ({}, {})\n", x, y); return false; } break; case embark_assist::defs::aquifer_ranges::At_Least_Light: if (tile->aquifer == embark_assist::defs::None_Aquifer_Bit) { - if (trace) out.print("matcher::world_tile_match: Aquifer At_Least_Light (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Aquifer At_Least_Light ({}, {})\n", x, y); return false; } break; @@ -1556,7 +1556,7 @@ namespace embark_assist { case embark_assist::defs::aquifer_ranges::None_Plus_Heavy: if (!(tile->aquifer & embark_assist::defs::None_Aquifer_Bit) || !(tile->aquifer & embark_assist::defs::Heavy_Aquifer_Bit)) { - if (trace) out.print("matcher::world_tile_match: Aquifer None_Plus_Heavy (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Aquifer None_Plus_Heavy ({}, {})\n", x, y); return false; } break; @@ -1564,7 +1564,7 @@ namespace embark_assist { case embark_assist::defs::aquifer_ranges::At_Most_Light_Plus_Heavy: if (tile->aquifer == embark_assist::defs::Heavy_Aquifer_Bit || !(tile->aquifer & embark_assist::defs::Heavy_Aquifer_Bit)) { - if (trace) out.print("matcher::world_tile_match: Aquifer At_Most_Light_Plus_Heavy (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Aquifer At_Most_Light_Plus_Heavy ({}, {})\n", x, y); return false; } break; @@ -1572,7 +1572,7 @@ namespace embark_assist { case embark_assist::defs::aquifer_ranges::Light_Plus_Heavy: if (!(tile->aquifer & embark_assist::defs::Light_Aquifer_Bit) || !(tile->aquifer & embark_assist::defs::Heavy_Aquifer_Bit)) { - if (trace) out.print("matcher::world_tile_match: Aquifer Light_Plus_Heavy (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Aquifer Light_Plus_Heavy ({}, {})\n", x, y); return false; } break; @@ -1580,14 +1580,14 @@ namespace embark_assist { case embark_assist::defs::aquifer_ranges::None_Light_Heavy: if (tile->aquifer != (embark_assist::defs::None_Aquifer_Bit | embark_assist::defs::Light_Aquifer_Bit | embark_assist::defs::Heavy_Aquifer_Bit)) { - if (trace) out.print("matcher::world_tile_match: Aquifer None_Light_Heavy (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Aquifer None_Light_Heavy ({}, {})\n", x, y); return false; } break; case embark_assist::defs::aquifer_ranges::Heavy: if (!(tile->aquifer & embark_assist::defs::Heavy_Aquifer_Bit)) { - if (trace) out.print("matcher::world_tile_match: Aquifer Heavy (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Aquifer Heavy ({}, {})\n", x, y); return false; } break; @@ -1597,42 +1597,42 @@ namespace embark_assist { switch (tile->max_river_size) { case embark_assist::defs::river_sizes::None: if (finder->min_river > embark_assist::defs::river_ranges::None) { - if (trace) out.print("matcher::world_tile_match: River_Size None (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: River_Size None ({}, {})\n", x, y); return false; } break; case embark_assist::defs::river_sizes::Brook: if (finder->min_river > embark_assist::defs::river_ranges::Brook) { - if (trace) out.print("matcher::world_tile_match: River_Size Brook (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: River_Size Brook ({}, {})\n", x, y); return false; } break; case embark_assist::defs::river_sizes::Stream: if (finder->min_river > embark_assist::defs::river_ranges::Stream) { - if (trace) out.print("matcher::world_tile_match: River_Size Stream (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: River_Size Stream ({}, {})\n", x, y); return false; } break; case embark_assist::defs::river_sizes::Minor: if (finder->min_river > embark_assist::defs::river_ranges::Minor) { - if (trace) out.print("matcher::world_tile_match: River_Size Mino (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: River_Size Mino ({}, {})\n", x, y); return false; } break; case embark_assist::defs::river_sizes::Medium: if (finder->min_river > embark_assist::defs::river_ranges::Medium) { - if (trace) out.print("matcher::world_tile_match: River_Size Medium (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: River_Size Medium ({}, {})\n", x, y); return false; } break; case embark_assist::defs::river_sizes::Major: if (finder->max_river != embark_assist::defs::river_ranges::NA) { - if (trace) out.print("matcher::world_tile_match: River_Size Major (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: River_Size Major ({}, {})\n", x, y); return false; } break; @@ -1640,13 +1640,13 @@ namespace embark_assist { // Waterfall if (finder->min_waterfall > tile->max_waterfall) { // N/A = -1 is always smaller - if (trace) out.print("matcher::world_tile_match: Waterfall (%i, %i), finder: %i, tile: %i\n", x, y, finder->min_waterfall, tile->max_waterfall); + if (trace) out.print("matcher::world_tile_match: Waterfall ({}, {}), finder: {}, tile: {}\n", x, y, finder->min_waterfall, tile->max_waterfall); return false; } if (finder->min_waterfall == 0 && // Absent embark_size == 256 && tile->max_waterfall > 0) { - if (trace) out.print("matcher::world_tile_match: Waterfall 2 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Waterfall 2 ({}, {})\n", x, y); return false; } @@ -1660,14 +1660,14 @@ namespace embark_assist { case embark_assist::defs::present_absent_ranges::Present: if (tile->clay_count == 0 && !tile->neighboring_clay) { - if (trace) out.print("matcher::world_tile_match: Clay Present (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Clay Present ({}, {})\n", x, y); return false; } break; case embark_assist::defs::present_absent_ranges::Absent: if (tile->clay_count > 256 - embark_size) { - if (trace) out.print("matcher::world_tile_match: Clay Absent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Clay Absent ({}, {})\n", x, y); return false; } break; @@ -1681,14 +1681,14 @@ namespace embark_assist { case embark_assist::defs::present_absent_ranges::Present: if (tile->sand_count == 0 && !tile->neighboring_sand) { - if (trace) out.print("matcher::world_tile_match: Sand Present (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Sand Present ({}, {})\n", x, y); return false; } break; case embark_assist::defs::present_absent_ranges::Absent: if (tile->sand_count > 256 - embark_size) { - if (trace) out.print("matcher::world_tile_match: Sand Absent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Sand Absent ({}, {})\n", x, y); return false; } break; @@ -1701,14 +1701,14 @@ namespace embark_assist { case embark_assist::defs::present_absent_ranges::Present: if (tile->flux_count == 0) { - if (trace) out.print("matcher::world_tile_match: Flux Present (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Flux Present ({}, {})\n", x, y); return false; } break; case embark_assist::defs::present_absent_ranges::Absent: if (tile->flux_count > 256 - embark_size) { - if (trace) out.print("matcher::world_tile_match: Flux Absent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Flux Absent ({}, {})\n", x, y); return false; } break; @@ -1721,14 +1721,14 @@ namespace embark_assist { case embark_assist::defs::present_absent_ranges::Present: if (tile->coal_count == 0) { - if (trace) out.print("matcher::world_tile_match: Coal Present (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Coal Present ({}, {})\n", x, y); return false; } break; case embark_assist::defs::present_absent_ranges::Absent: if (tile->coal_count > 256 - embark_size) { - if (trace) out.print("matcher::world_tile_match: Coal Absent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Coal Absent ({}, {})\n", x, y); return false; } break; @@ -1742,28 +1742,28 @@ namespace embark_assist { case embark_assist::defs::soil_ranges::Very_Shallow: if (tile->max_region_soil < 1) { - if (trace) out.print("matcher::world_tile_match: Soil Min Very Shallow (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Soil Min Very Shallow ({}, {})\n", x, y); return false; } break; case embark_assist::defs::soil_ranges::Shallow: if (tile->max_region_soil < 2) { - if (trace) out.print("matcher::world_tile_match: Soil Min Shallow (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Soil Min Shallow ({}, {})\n", x, y); return false; } break; case embark_assist::defs::soil_ranges::Deep: if (tile->max_region_soil < 3) { - if (trace) out.print("matcher::world_tile_match: Soil Min Deep (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Soil Min Deep ({}, {})\n", x, y); return false; } break; case embark_assist::defs::soil_ranges::Very_Deep: if (tile->max_region_soil < 4) { - if (trace) out.print("matcher::world_tile_match: Soil Min Very Deep (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Soil Min Very Deep ({}, {})\n", x, y); return false; } break; @@ -1779,28 +1779,28 @@ namespace embark_assist { case embark_assist::defs::soil_ranges::None: if (tile->min_region_soil > 0) { - if (trace) out.print("matcher::world_tile_match: Soil_Max None (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Soil_Max None ({}, {})\n", x, y); return false; } break; case embark_assist::defs::soil_ranges::Very_Shallow: if (tile->min_region_soil > 1) { - if (trace) out.print("matcher::world_tile_match: Soil_Max Very_Shallow (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Soil_Max Very_Shallow ({}, {})\n", x, y); return false; } break; case embark_assist::defs::soil_ranges::Shallow: if (tile->min_region_soil > 2) { - if (trace) out.print("matcher::world_tile_match: Soil_Max Shallow (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Soil_Max Shallow ({}, {})\n", x, y); return false; } break; case embark_assist::defs::soil_ranges::Deep: if (tile->min_region_soil > 3) { - if (trace) out.print("matcher::world_tile_match: Soil_Max Deep (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Soil_Max Deep ({}, {})\n", x, y); return false; } break; @@ -1840,14 +1840,14 @@ namespace embark_assist { case embark_assist::defs::freezing_ranges::Permanent: if (min_max_temperature > 0) { - if (trace) out.print("matcher::world_tile_match: Freezing Permanent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Freezing Permanent ({}, {})\n", x, y); return false; } break; case embark_assist::defs::freezing_ranges::At_Least_Partial: if (min_min_temperature > 0) { - if (trace) out.print("matcher::world_tile_match: Freezing At_Lest_Partial (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Freezing At_Lest_Partial ({}, {})\n", x, y); return false; } break; @@ -1855,21 +1855,21 @@ namespace embark_assist { case embark_assist::defs::freezing_ranges::Partial: if (min_min_temperature > 0 || max_max_temperature <= 0) { - if (trace) out.print("matcher::world_tile_match: Freezing Partial (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Freezing Partial ({}, {})\n", x, y); return false; } break; case embark_assist::defs::freezing_ranges::At_Most_Partial: if (max_max_temperature <= 0) { - if (trace) out.print("matcher::world_tile_match: Freezing At Most_Partial (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Freezing At Most_Partial ({}, {})\n", x, y); return false; } break; case embark_assist::defs::freezing_ranges::Never: if (max_min_temperature <= 0) { - if (trace) out.print("matcher::world_tile_match: Freezing Never (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Freezing Never ({}, {})\n", x, y); return false; } break; @@ -1884,28 +1884,28 @@ namespace embark_assist { case embark_assist::defs::tree_ranges::Very_Scarce: if (tile->max_tree_level < embark_assist::defs::tree_levels::Very_Scarce) { - if (trace) out.print("matcher::world_tile_match: Min_Trees Very_Scarce (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Min_Trees Very_Scarce ({}, {})\n", x, y); return false; } break; case embark_assist::defs::tree_ranges::Scarce: if (tile->max_tree_level < embark_assist::defs::tree_levels::Scarce) { - if (trace) out.print("matcher::world_tile_match: Min_Trees Scarce (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Min_Trees Scarce ({}, {})\n", x, y); return false; } break; case embark_assist::defs::tree_ranges::Woodland: if (tile->max_tree_level < embark_assist::defs::tree_levels::Woodland) { - if (trace) out.print("matcher::world_tile_match: Min_Trees Woodland (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Min_Trees Woodland ({}, {})\n", x, y); return false; } break; case embark_assist::defs::tree_ranges::Heavily_Forested: if (tile->max_tree_level < embark_assist::defs::tree_levels::Heavily_Forested) { - if (trace) out.print("matcher::world_tile_match: Min_Trees Heavily_Forested (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Min_Trees Heavily_Forested ({}, {})\n", x, y); return false; } break; @@ -1918,7 +1918,7 @@ namespace embark_assist { case embark_assist::defs::tree_ranges::None: if (tile->min_tree_level > embark_assist::defs::tree_levels::None) { - if (trace) out.print("matcher::world_tile_match: Max_Trees None (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Max_Trees None ({}, {})\n", x, y); return false; } break; @@ -1926,21 +1926,21 @@ namespace embark_assist { case embark_assist::defs::tree_ranges::Very_Scarce: if (tile->min_tree_level > embark_assist::defs::tree_levels::Very_Scarce) { - if (trace) out.print("matcher::world_tile_match: Max_Trees Very_Scarce (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Max_Trees Very_Scarce ({}, {})\n", x, y); return false; } break; case embark_assist::defs::tree_ranges::Scarce: if (tile->min_tree_level > embark_assist::defs::tree_levels::Scarce) { - if (trace) out.print("matcher::world_tile_match: Max_Trees Scarce (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Max_Trees Scarce ({}, {})\n", x, y); return false; } break; case embark_assist::defs::tree_ranges::Woodland: if (tile->min_tree_level > embark_assist::defs::tree_levels::Woodland) { - if (trace) out.print("matcher::world_tile_match: Max_Trees Woodland (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Max_Trees Woodland ({}, {})\n", x, y); return false; } break; @@ -1953,14 +1953,14 @@ namespace embark_assist { case embark_assist::defs::yes_no_ranges::Yes: if (!tile->blood_rain_possible) { - if (trace) out.print("matcher::world_tile_match: Blood_Rain Yes (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Blood_Rain Yes ({}, {})\n", x, y); return false; } break; case embark_assist::defs::yes_no_ranges::No: if (tile->blood_rain_full) { - if (trace) out.print("matcher::world_tile_match: Blood_Rain No (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Blood_Rain No ({}, {})\n", x, y); return false; } break; @@ -1973,35 +1973,35 @@ namespace embark_assist { case embark_assist::defs::syndrome_rain_ranges::Any: if (!tile->permanent_syndrome_rain_possible && !tile->temporary_syndrome_rain_possible) { - if (trace) out.print("matcher::world_tile_match: Syndrome_Rain Any (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Syndrome_Rain Any ({}, {})\n", x, y); return false; } break; case embark_assist::defs::syndrome_rain_ranges::Permanent: if (!tile->permanent_syndrome_rain_possible) { - if (trace) out.print("matcher::world_tile_match: Syndrome_Rain Permanent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Syndrome_Rain Permanent ({}, {})\n", x, y); return false; } break; case embark_assist::defs::syndrome_rain_ranges::Temporary: if (!tile->temporary_syndrome_rain_possible) { - if (trace) out.print("matcher::world_tile_match: Syndrome_Rain Temporary (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Syndrome_Rain Temporary ({}, {})\n", x, y); return false; } break; case embark_assist::defs::syndrome_rain_ranges::Not_Permanent: if (tile->permanent_syndrome_rain_full) { - if (trace) out.print("matcher::world_tile_match: Syndrome_Rain Not_Permanent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Syndrome_Rain Not_Permanent ({}, {})\n", x, y); return false; } break; case embark_assist::defs::syndrome_rain_ranges::None: if (tile->permanent_syndrome_rain_full || tile->temporary_syndrome_rain_full) { - if (trace) out.print("matcher::world_tile_match: Syndrome_Rain None (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Syndrome_Rain None ({}, {})\n", x, y); return false; } break; @@ -2014,42 +2014,42 @@ namespace embark_assist { case embark_assist::defs::reanimation_ranges::Both: if (!tile->reanimating_possible || !tile->thralling_possible) { - if (trace) out.print("matcher::world_tile_match: Reanimation Both (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Reanimation Both ({}, {})\n", x, y); return false; } break; case embark_assist::defs::reanimation_ranges::Any: if (!tile->reanimating_possible && !tile->thralling_possible) { - if (trace) out.print("matcher::world_tile_match: Reanimation Any (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Reanimation Any ({}, {})\n", x, y); return false; } break; case embark_assist::defs::reanimation_ranges::Thralling: if (!tile->thralling_possible) { - if (trace) out.print("matcher::world_tile_match: Reanimation Thralling (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Reanimation Thralling ({}, {})\n", x, y); return false; } break; case embark_assist::defs::reanimation_ranges::Reanimation: if (!tile->reanimating_possible) { - if (trace) out.print("matcher::world_tile_match: Reanimation Reanimation (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Reanimation Reanimation ({}, {})\n", x, y); return false; } break; case embark_assist::defs::reanimation_ranges::Not_Thralling: if (tile->thralling_full) { - if (trace) out.print("matcher::world_tile_match: Reanimation Not_Thralling (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Reanimation Not_Thralling ({}, {})\n", x, y); return false; } break; case embark_assist::defs::reanimation_ranges::None: if (tile->reanimating_full || tile->thralling_full) { - if (trace) out.print("matcher::world_tile_match: Reanimation None (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Reanimation None ({}, {})\n", x, y); return false; } break; @@ -2063,7 +2063,7 @@ namespace embark_assist { // Region Type 1 if (finder->region_type_1 != -1) { if (!tile->neighboring_region_types[finder->region_type_1]) { - if (trace) out.print("matcher::world_tile_match: Region_Type_1 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Region_Type_1 ({}, {})\n", x, y); return false; } } @@ -2071,7 +2071,7 @@ namespace embark_assist { // Region Type 2 if (finder->region_type_2 != -1) { if (!tile->neighboring_region_types[finder->region_type_2]) { - if (trace) out.print("matcher::world_tile_match: Region_Type_2 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Region_Type_2 ({}, {})\n", x, y); return false; } } @@ -2079,7 +2079,7 @@ namespace embark_assist { // Region Type 3 if (finder->region_type_3 != -1) { if (!tile->neighboring_region_types[finder->region_type_3]) { - if (trace) out.print("matcher::world_tile_match: Region_Type_3 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Region_Type_3 ({}, {})\n", x, y); return false; } } @@ -2087,7 +2087,7 @@ namespace embark_assist { // Biome 1 if (finder->biome_1 != -1) { if (!tile->neighboring_biomes[finder->biome_1]) { - if (trace) out.print("matcher::world_tile_match: Biome_1 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Biome_1 ({}, {})\n", x, y); return false; } } @@ -2095,7 +2095,7 @@ namespace embark_assist { // Biome 2 if (finder->biome_2 != -1) { if (!tile->neighboring_biomes[finder->biome_2]) { - if (trace) out.print("matcher::world_tile_match: Biome_2 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Biome_2 ({}, {})\n", x, y); return false; } } @@ -2103,7 +2103,7 @@ namespace embark_assist { // Biome 3 if (finder->biome_3 != -1) { if (!tile->neighboring_biomes[finder->biome_3]) { - if (trace) out.print("matcher::world_tile_match: Biome_3 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Biome_3 ({}, {})\n", x, y); return false; } } @@ -2146,30 +2146,30 @@ namespace embark_assist { !mineral_1 || !mineral_2 || !mineral_3) { - if (trace) out.print("matcher::world_tile_match: Metal/Economic/Mineral (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Metal/Economic/Mineral ({}, {})\n", x, y); return false; } } // Necro Neighbors if (finder->min_necro_neighbors > tile->necro_neighbors) { - if (trace) out.print("matcher::world_tile_match: Necro_Neighbors 1 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Necro_Neighbors 1 ({}, {})\n", x, y); return false; } if (finder->max_necro_neighbors < tile->necro_neighbors && finder->max_necro_neighbors != -1) { - if (trace) out.print("matcher::world_tile_match: Necro_Neighbors 2 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Necro_Neighbors 2 ({}, {})\n", x, y); return false; } // Civ Neighbors if (finder->min_civ_neighbors > (int16_t)tile->neighbors.size()) { - if (trace) out.print("matcher::world_tile_match: Civ_Neighbors 1 (%i, %i), %i, %i\n", x, y, finder->min_civ_neighbors, (int)tile->neighbors.size()); + if (trace) out.print("matcher::world_tile_match: Civ_Neighbors 1 ({}, {}), {}, {}\n", x, y, finder->min_civ_neighbors, (int)tile->neighbors.size()); return false; } if (finder->max_civ_neighbors < (int8_t)tile->neighbors.size() && finder->max_civ_neighbors != -1) { - if (trace) out.print("matcher::world_tile_match: Civ_Neighbors 2 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Civ_Neighbors 2 ({}, {})\n", x, y); return false; } @@ -2191,7 +2191,7 @@ namespace embark_assist { } if (!found) { - if (trace) out.print("matcher::world_tile_match: Specific Neighbors Present (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Specific Neighbors Present ({}, {})\n", x, y); return false; } @@ -2201,7 +2201,7 @@ namespace embark_assist { case embark_assist::defs::present_absent_ranges::Absent: for (uint16_t k = 0; k < tile->neighbors.size(); k++) { if (finder->neighbors[i].entity_raw == tile->neighbors[k]) { - if (trace) out.print("matcher::world_tile_match: Specific Neighbors Absent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: Specific Neighbors Absent ({}, {})\n", x, y); return false; } } @@ -2223,21 +2223,21 @@ namespace embark_assist { case embark_assist::defs::evil_savagery_values::All: if (tile->savagery_count[i] == 0) { - if (trace) out.print("matcher::world_tile_match: NS Savagery All (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Savagery All ({}, {})\n", x, y); return false; } break; case embark_assist::defs::evil_savagery_values::Present: if (tile->savagery_count[i] == 0) { - if (trace) out.print("matcher::world_tile_match: NS Savagery Present (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Savagery Present ({}, {})\n", x, y); return false; } break; case embark_assist::defs::evil_savagery_values::Absent: if (tile->savagery_count[i] == 256) { - if (trace) out.print("matcher::world_tile_match: NS Savagery Absent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Savagery Absent ({}, {})\n", x, y); return false; } break; @@ -2253,21 +2253,21 @@ namespace embark_assist { case embark_assist::defs::evil_savagery_values::All: if (tile->evilness_count[i] == 0) { - if (trace) out.print("matcher::world_tile_match: NS Evil All (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Evil All ({}, {})\n", x, y); return false; } break; case embark_assist::defs::evil_savagery_values::Present: if (tile->evilness_count[i] == 0) { - if (trace) out.print("matcher::world_tile_match: NS Evil Present (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Evil Present ({}, {})\n", x, y); return false; } break; case embark_assist::defs::evil_savagery_values::Absent: if (tile->evilness_count[i] == 256) { - if (trace) out.print("matcher::world_tile_match: NS Evil Absent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Evil Absent ({}, {})\n", x, y); return false; } break; @@ -2295,14 +2295,14 @@ namespace embark_assist { case embark_assist::defs::present_absent_ranges::Present: if (tile->flux_count == 0) { - if (trace) out.print("matcher::world_tile_match: NS Flux Present (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Flux Present ({}, {})\n", x, y); return false; } break; case embark_assist::defs::present_absent_ranges::Absent: if (tile->flux_count == 256) { - if (trace) out.print("matcher::world_tile_match: NS Flux Absent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Flux Absent ({}, {})\n", x, y); return false; } break; @@ -2315,14 +2315,14 @@ namespace embark_assist { case embark_assist::defs::present_absent_ranges::Present: if (tile->coal_count == 0) { - if (trace) out.print("matcher::world_tile_match: NS Coal Present (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Coal Present ({}, {})\n", x, y); return false; } break; case embark_assist::defs::present_absent_ranges::Absent: if (tile->coal_count == 256) { - if (trace) out.print("matcher::world_tile_match: NS Coal Absent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Coal Absent ({}, {})\n", x, y); return false; } break; @@ -2336,28 +2336,28 @@ namespace embark_assist { case embark_assist::defs::soil_ranges::Very_Shallow: if (tile->max_region_soil < 1) { - if (trace) out.print("matcher::world_tile_match: NS Soil_Min Very_Shallow (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Soil_Min Very_Shallow ({}, {})\n", x, y); return false; } break; case embark_assist::defs::soil_ranges::Shallow: if (tile->max_region_soil < 2) { - if (trace) out.print("matcher::world_tile_match: NS Soil_Min Shallow (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Soil_Min Shallow ({}, {})\n", x, y); return false; } break; case embark_assist::defs::soil_ranges::Deep: if (tile->max_region_soil < 3) { - if (trace) out.print("matcher::world_tile_match: NS Soil_Min Deep (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Soil_Min Deep ({}, {})\n", x, y); return false; } break; case embark_assist::defs::soil_ranges::Very_Deep: if (tile->max_region_soil < 4) { - if (trace) out.print("matcher::world_tile_match: NS Soil_Min Very_Deep (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Soil_Min Very_Deep ({}, {})\n", x, y); return false; } break; @@ -2375,14 +2375,14 @@ namespace embark_assist { case embark_assist::defs::yes_no_ranges::Yes: if (!tile->blood_rain_possible) { - if (trace) out.print("matcher::world_tile_match: NS Blood_Rain Yes (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Blood_Rain Yes ({}, {})\n", x, y); return false; } break; case embark_assist::defs::yes_no_ranges::No: if (tile->blood_rain_full) { - if (trace) out.print("matcher::world_tile_match: NS Blood_Rain No (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Blood_Rain No ({}, {})\n", x, y); return false; } break; @@ -2397,35 +2397,35 @@ namespace embark_assist { case embark_assist::defs::syndrome_rain_ranges::Any: if (!tile->permanent_syndrome_rain_possible && !tile->temporary_syndrome_rain_possible) { - if (trace) out.print("matcher::world_tile_match: NS Syndrome_Rain Any (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Syndrome_Rain Any ({}, {})\n", x, y); return false; } break; case embark_assist::defs::syndrome_rain_ranges::Permanent: if (!tile->permanent_syndrome_rain_possible) { - if (trace) out.print("matcher::world_tile_match: NS Syndrome_Rain Permanent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Syndrome_Rain Permanent ({}, {})\n", x, y); return false; } break; case embark_assist::defs::syndrome_rain_ranges::Temporary: if (!tile->temporary_syndrome_rain_possible) { - if (trace) out.print("matcher::world_tile_match: NS Syndrome_Rain Temporary (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Syndrome_Rain Temporary ({}, {})\n", x, y); return false; } break; case embark_assist::defs::syndrome_rain_ranges::Not_Permanent: if (tile->permanent_syndrome_rain_full) { - if (trace) out.print("matcher::world_tile_match: NS Syndrome_Rain Not_Permanent (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Syndrome_Rain Not_Permanent ({}, {})\n", x, y); return false; } break; case embark_assist::defs::syndrome_rain_ranges::None: if (tile->permanent_syndrome_rain_full || tile->temporary_syndrome_rain_full) { - if (trace) out.print("matcher::world_tile_match: NS Syndrome_Rain None (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Syndrome_Rain None ({}, {})\n", x, y); return false; } break; @@ -2438,42 +2438,42 @@ namespace embark_assist { case embark_assist::defs::reanimation_ranges::Both: if (!tile->reanimating_possible || !tile->thralling_possible) { - if (trace) out.print("matcher::world_tile_match: NS Reanimating Both (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Reanimating Both ({}, {})\n", x, y); return false; } break; case embark_assist::defs::reanimation_ranges::Any: if (!tile->reanimating_possible && !tile->thralling_possible) { - if (trace) out.print("matcher::world_tile_match: NS Reanimating Any (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Reanimating Any ({}, {})\n", x, y); return false; } break; case embark_assist::defs::reanimation_ranges::Thralling: if (!tile->thralling_possible) { - if (trace) out.print("matcher::world_tile_match: NS Reanimating Thralling (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Reanimating Thralling ({}, {})\n", x, y); return false; } break; case embark_assist::defs::reanimation_ranges::Reanimation: if (!tile->reanimating_possible) { - if (trace) out.print("matcher::world_tile_match:NS Reanimating Reanimating (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match:NS Reanimating Reanimating ({}, {})\n", x, y); return false; } break; case embark_assist::defs::reanimation_ranges::Not_Thralling: if (tile->thralling_full) { - if (trace) out.print("matcher::world_tile_match: NS Reanimating Not_Thralling (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Reanimating Not_Thralling ({}, {})\n", x, y); return false; } break; case embark_assist::defs::reanimation_ranges::None: if (tile->reanimating_full || tile->thralling_full) { - if (trace) out.print("matcher::world_tile_match: NS Reanimating None (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Reanimating None ({}, {})\n", x, y); return false; } break; @@ -2483,7 +2483,7 @@ namespace embark_assist { // Magma Min/Max // Biome Count Min (Can't do anything with Max at this level) if (finder->biome_count_min > tile->biome_count) { - if (trace) out.print("matcher::world_tile_match: NS Biome_Count (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Biome_Count ({}, {})\n", x, y); return false; } @@ -2503,7 +2503,7 @@ namespace embark_assist { } if (!found) { - if (trace) out.print("matcher::world_tile_match: NS Region_Type_1 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Region_Type_1 ({}, {})\n", x, y); return false; } } @@ -2524,7 +2524,7 @@ namespace embark_assist { } if (!found) { - if (trace) out.print("matcher::world_tile_match: NS Region_Type_2 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Region_Type_2 ({}, {})\n", x, y); return false; } } @@ -2545,7 +2545,7 @@ namespace embark_assist { } if (!found) { - if (trace) out.print("matcher::world_tile_match: NS Region_Type_3 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Region_Type_3 ({}, {})\n", x, y); return false; } } @@ -2562,7 +2562,7 @@ namespace embark_assist { } if (!found) { - if (trace) out.print("matcher::world_tile_match: NS Biome_1 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Biome_1 ({}, {})\n", x, y); return false; } } @@ -2579,7 +2579,7 @@ namespace embark_assist { } if (!found) { - if (trace) out.print("matcher::world_tile_match: NS Biome_2 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Biome_2 ({}, {})\n", x, y); return false; } } @@ -2596,7 +2596,7 @@ namespace embark_assist { } if (!found) { - if (trace) out.print("matcher::world_tile_match: NS Biome_3 (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Biome_3 ({}, {})\n", x, y); return false; } } @@ -2639,7 +2639,7 @@ namespace embark_assist { !mineral_1 || !mineral_2 || !mineral_3) { - if (trace) out.print("matcher::world_tile_match: NS Metal/Economic/Mineral (%i, %i)\n", x, y); + if (trace) out.print("matcher::world_tile_match: NS Metal/Economic/Mineral ({}, {})\n", x, y); return false; } } @@ -2948,11 +2948,11 @@ uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iter preliminary_matches = preliminary_world_match(survey_results, &iterator->finder, match_results); if (preliminary_matches == 0) { - out.printerr("matcher::find: Preliminarily matching World Tiles: %i\n", preliminary_matches); + out.printerr("matcher::find: Preliminarily matching World Tiles: {}\n", preliminary_matches); return 0; } else { - out.print("matcher::find: Preliminarily matching World Tiles: %i\n", preliminary_matches); + out.print("matcher::find: Preliminarily matching World Tiles: {}\n", preliminary_matches); } while (screen->location.region_pos.x != 0 || screen->location.region_pos.y != 0) { diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 0ed06d9284..8dd3bbc85d 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -22,6 +22,7 @@ #include "df/unit_wound.h" #include "df/world.h" +#include #include #include #include diff --git a/plugins/examples/persistent_per_save_example.cpp b/plugins/examples/persistent_per_save_example.cpp index 3ca26fc1d3..4067e1d161 100644 --- a/plugins/examples/persistent_per_save_example.cpp +++ b/plugins/examples/persistent_per_save_example.cpp @@ -56,14 +56,14 @@ static PersistentDataItem & ensure_elem_config(color_ostream &out, int id) { if (elems.count(id)) return elems[id]; string keyname = ELEM_CONFIG_KEY_PREFIX + int_to_string(id); - DEBUG(control,out).print("creating new persistent key for elem id %d\n", id); + DEBUG(control,out).print("creating new persistent key for elem id {}\n", id); elems.emplace(id, World::GetPersistentSiteData(keyname, true)); return elems[id]; } static void remove_elem_config(color_ostream &out, int id) { if (!elems.count(id)) return; - DEBUG(control,out).print("removing persistent key for elem id %d\n", id); + DEBUG(control,out).print("removing persistent key for elem id {}\n", id); World::DeletePersistentData(elems[id]); elems.erase(id); } @@ -82,7 +82,7 @@ static command_result do_command(color_ostream &out, vector ¶meters) static void do_cycle(color_ostream &out); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(control,out).print("initializing %s\n", plugin_name); + DEBUG(control,out).print("initializing {}\n", plugin_name); // provide a configuration interface for the plugin commands.push_back(PluginCommand( @@ -95,19 +95,19 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::IsSiteLoaded()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -190,7 +190,7 @@ static void do_cycle(color_ostream &out) { // mark that we have recently run cycle_timestamp = world->frame_counter; - DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + DEBUG(cycle,out).print("running {} cycle\n", plugin_name); // TODO: logic that runs every CYCLE_TICKS ticks } diff --git a/plugins/examples/simple_command_example.cpp b/plugins/examples/simple_command_example.cpp index ea45b18d47..d708bc222f 100644 --- a/plugins/examples/simple_command_example.cpp +++ b/plugins/examples/simple_command_example.cpp @@ -21,7 +21,7 @@ namespace DFHack { static command_result do_command(color_ostream &out, vector ¶meters); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(log,out).print("initializing %s\n", plugin_name); + DEBUG(log,out).print("initializing {}\n", plugin_name); commands.push_back(PluginCommand( plugin_name, diff --git a/plugins/examples/skeleton.cpp b/plugins/examples/skeleton.cpp index fd77ab691a..a6b9e234a3 100644 --- a/plugins/examples/skeleton.cpp +++ b/plugins/examples/skeleton.cpp @@ -66,7 +66,7 @@ static command_result command_callback1(color_ostream &out, vector ¶ // run when the plugin is loaded DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(status,out).print("initializing %s\n", plugin_name); + DEBUG(status,out).print("initializing {}\n", plugin_name); // For in-tree plugins, don't use the "usage" parameter of PluginCommand. // Instead, add an .rst file with the same name as the plugin to the @@ -80,7 +80,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector ¶meters) { - DEBUG(command,out).print("%s command called with %zu parameters\n", + DEBUG(command,out).print("{} command called with {} parameters\n", plugin_name, parameters.size()); // Return CR_WRONG_USAGE to print out your help text. The help text is diff --git a/plugins/examples/ui_addition_example.cpp b/plugins/examples/ui_addition_example.cpp index bbd3af3deb..95bcd16455 100644 --- a/plugins/examples/ui_addition_example.cpp +++ b/plugins/examples/ui_addition_example.cpp @@ -39,14 +39,14 @@ struct title_version_hook : df::viewscreen_titlest { IMPLEMENT_VMETHOD_INTERPOSE(title_version_hook, render); DFhackCExport command_result plugin_shutdown (color_ostream &out) { - DEBUG(log,out).print("shutting down %s\n", plugin_name); + DEBUG(log,out).print("shutting down {}\n", plugin_name); INTERPOSE_HOOK(title_version_hook, render).remove(); return CR_OK; } DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) { if (enable != is_enabled) { - DEBUG(log,out).print("%s %s\n", plugin_name, + DEBUG(log,out).print("{} {}\n", plugin_name, is_enabled ? "enabled" : "disabled"); if (!INTERPOSE_HOOK(title_version_hook, render).apply(enable)) return CR_FAILURE; diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 0d4808db8c..d1a6750a5d 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -37,7 +37,7 @@ enum ConfigValues { static command_result do_command(color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init(color_ostream &out, std::vector & commands) { - DEBUG(control,out).print("initializing %s\n", plugin_name); + DEBUG(control,out).print("initializing {}\n", plugin_name); commands.push_back(PluginCommand( "fastdwarf", @@ -48,7 +48,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vectorpath.dest)) return; - DEBUG(cycle,out).print("teleporting unit %d\n", unit->id); + DEBUG(cycle,out).print("teleporting unit {}\n", unit->id); if (!Units::teleport(unit, unit->path.dest)) return; @@ -150,7 +150,7 @@ static void do_tele(color_ostream &out, df::unit * unit) { } DFhackCExport command_result plugin_onupdate(color_ostream &out) { - DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + DEBUG(cycle,out).print("running {} cycle\n", plugin_name); // fast mode 2 is handled by DF itself bool is_fast = config.get_int(CONFIG_FAST) == 1; @@ -161,7 +161,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { do_tele(out, unit); if (is_fast) { - DEBUG(cycle,out).print("fastifying unit %d\n", unit->id); + DEBUG(cycle,out).print("fastifying unit {}\n", unit->id); Units::setGroupActionTimers(out, unit, 1, df::unit_action_type_group::All); } }); @@ -176,7 +176,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { static command_result do_command(color_ostream &out, vector & parameters) { if (!Core::getInstance().isMapLoaded() || !World::IsSiteLoaded()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -186,7 +186,7 @@ static command_result do_command(color_ostream &out, vector & parameter return CR_WRONG_USAGE; if (num_params == 0 || parameters[0] == "status") { - out.print("Current state: fast = %d, teleport = %d.\n", + out.print("Current state: fast = {}, teleport = {}.\n", config.get_int(CONFIG_FAST), config.get_int(CONFIG_TELE)); return CR_OK; @@ -196,11 +196,11 @@ static command_result do_command(color_ostream &out, vector & parameter int tele = num_params >= 2 ? string_to_int(parameters[1]) : 0; if (fast < 0 || fast > 2) { - out.printerr("Invalid value for fast: '%s'", parameters[0].c_str()); + out.printerr("Invalid value for fast: '{}'", parameters[0]); return CR_WRONG_USAGE; } if (tele < 0 || tele > 1) { - out.printerr("Invalid value for tele: '%s'", parameters[1].c_str()); + out.printerr("Invalid value for tele: '{}'", parameters[1]); return CR_WRONG_USAGE; } diff --git a/plugins/filltraffic.cpp b/plugins/filltraffic.cpp index c8b0a209e3..23952d8ac2 100644 --- a/plugins/filltraffic.cpp +++ b/plugins/filltraffic.cpp @@ -1,6 +1,7 @@ // Wide-area traffic designation utility. // Flood-fill from cursor or fill entire map. +#include #include //For toupper(). #include //for min(). #include @@ -157,7 +158,7 @@ command_result filltraffic(color_ostream &out, std::vector & params return CR_FAILURE; } - out.print("%d/%d/%d ... FILLING!\n", cx,cy,cz); + out.print("{}/{}/{} ... FILLING!\n", cx,cy,cz); //Naive four-way or six-way flood fill with possible tiles on a stack. stack flood; diff --git a/plugins/fix-occupancy.cpp b/plugins/fix-occupancy.cpp index 7b5a416dbb..5ed962e4d4 100644 --- a/plugins/fix-occupancy.cpp +++ b/plugins/fix-occupancy.cpp @@ -107,8 +107,8 @@ static void scan_building(color_ostream &out, df::building * bld, Expected & exp auto expected_bld = expected.bld(x, y, bld->z); if (bld->isSettingOccupancy() && expected_bld) { if (*expected_bld) { - WARN(log,out).print("Buildings overlap at (%d, %d, %d); please manually remove overlapping building." - " Run ':lua dfhack.gui.revealInDwarfmodeMap(%d, %d, %d, true, true)' to zoom to the tile.\n", + WARN(log,out).print("Buildings overlap at ({}, {}, {}); please manually remove overlapping building." + " Run ':lua dfhack.gui.revealInDwarfmodeMap({}, {}, {}, true, true)' to zoom to the tile.\n", x, y, bld->z, x, y, bld->z); } *expected_bld = bld; @@ -163,7 +163,7 @@ static void normalize_item_vector(color_ostream &out, df::map_block *block, bool prev_id = item_id; } if (needs_sorting) { - INFO(log,out).print("%s item list for map block at (%d, %d, %d)\n", + INFO(log,out).print("{} item list for map block at ({}, {}, {})\n", dry_run ? "would fix" : "fixing", block->map_pos.x, block->map_pos.y, block->map_pos.z); if (!dry_run) std::sort(block->items.begin(), block->items.end()); @@ -175,7 +175,7 @@ static void reconcile_map_tile(color_ostream &out, df::building * bld, const df: { // clear building occupancy if there is no building there if (expected_occ.bits.building == df::tile_building_occ::None && block_occ.bits.building != df::tile_building_occ::None) { - INFO(log,out).print("%s building occupancy at (%d, %d, %d)\n", + INFO(log,out).print("{} building occupancy at ({}, {}, {})\n", dry_run ? "would fix" : "fixing", x, y, z); if (!dry_run) block_occ.bits.building = df::tile_building_occ::None; @@ -189,7 +189,7 @@ static void reconcile_map_tile(color_ostream &out, df::building * bld, const df: if (block_occ.bits.building == df::tile_building_occ::Dynamic) block_occ.bits.building = prev_occ; else if (prev_occ != block_occ.bits.building) { - INFO(log,out).print("%s building occupancy at (%d, %d, %d)\n", + INFO(log,out).print("{} building occupancy at ({}, {}, {})\n", dry_run ? "would fix" : "fixing", x, y, z); if (dry_run) block_occ.bits.building = prev_occ; @@ -198,13 +198,13 @@ static void reconcile_map_tile(color_ostream &out, df::building * bld, const df: // clear unit occupancy if there are no units there if (!expected_occ.bits.unit && block_occ.bits.unit) { - INFO(log,out).print("%s standing unit occupancy at (%d, %d, %d)\n", + INFO(log,out).print("{} standing unit occupancy at ({}, {}, {})\n", dry_run ? "would fix" : "fixing", x, y, z); if (!dry_run) block_occ.bits.unit = false; } if (!expected_occ.bits.unit_grounded && block_occ.bits.unit_grounded) { - INFO(log,out).print("%s grounded unit occupancy at (%d, %d, %d)\n", + INFO(log,out).print("{} grounded unit occupancy at ({}, {}, {})\n", dry_run ? "would fix" : "fixing", x, y, z); if (!dry_run) block_occ.bits.unit_grounded = false; @@ -212,7 +212,7 @@ static void reconcile_map_tile(color_ostream &out, df::building * bld, const df: // clear item occupancy if there are no items there if (!expected_occ.bits.item && block_occ.bits.item) { - INFO(log,out).print("%s item occupancy at (%d, %d, %d)\n", + INFO(log,out).print("{} item occupancy at ({}, {}, {})\n", dry_run ? "would fix" : "fixing", x, y, z); if (!dry_run) block_occ.bits.item = false; @@ -223,7 +223,7 @@ static void fix_tile(color_ostream &out, df::coord pos, bool dry_run) { auto occ = Maps::getTileOccupancy(pos); auto block = Maps::getTileBlock(pos); if (!occ || !block) { - WARN(log,out).print("invalid tile: (%d, %d, %d)\n", pos.x, pos.y, pos.z); + WARN(log,out).print("invalid tile: ({}, {}, {})\n", pos.x, pos.y, pos.z); return; } @@ -264,7 +264,7 @@ static void fix_tile(color_ostream &out, df::coord pos, bool dry_run) { if (expected_bld && expected_occ) reconcile_map_tile(out, *expected_bld, *expected_occ, block->occupancy[pos.x&15][pos.y&15], dry_run, pos.x, pos.y, pos.z); - INFO(log,out).print("verified %zd building(s), %zd unit(s), %zd item(s), 1 map block(s), and 1 map tile(s)\n", + INFO(log,out).print("verified {} building(s), {} unit(s), {} item(s), 1 map block(s), and 1 map tile(s)\n", num_buildings, num_units, num_items); } @@ -273,7 +273,7 @@ static void reconcile_block_items(color_ostream &out, std::set * expect if (!expected_items) { if (block_items.size()) { - INFO(log,out).print("%s stale item references in map block at (%d, %d, %d)\n", + INFO(log,out).print("{} stale item references in map block at ({}, {}, {})\n", dry_run ? "would fix" : "fixing", block->map_pos.x, block->map_pos.y, block->map_pos.z); if (!dry_run) block_items.resize(0); @@ -282,7 +282,7 @@ static void reconcile_block_items(color_ostream &out, std::set * expect } if (!std::equal(expected_items->begin(), expected_items->end(), block_items.begin(), block_items.end())) { - INFO(log,out).print("%s stale item references in map block at (%d, %d, %d)\n", + INFO(log,out).print("{} stale item references in map block at ({}, {}, {})\n", dry_run ? "would fix" : "fixing", block->map_pos.x, block->map_pos.y, block->map_pos.z); if (!dry_run) { block_items.resize(expected_items->size()); @@ -324,20 +324,20 @@ static void fix_map(color_ostream &out, bool dry_run) { auto expected_occ = expected.occ(x, y, z); auto expected_bld = expected.bld(x, y, z); if (!expected_occ || !expected_bld) { - TRACE(log,out).print("pos out of bounds (%d, %d, %d)\n", x, y, z); + TRACE(log,out).print("pos out of bounds ({}, {}, {})\n", x, y, z); continue; } df::tile_occupancy &block_occ = block->occupancy[xoff][yoff]; if (*expected_bld || (expected_occ->whole & occ_mask) != (block_occ.whole & occ_mask)) { - DEBUG(log,out).print("reconciling occupancy at (%d, %d, %d) (bld=%p, 0x%x ?= 0x%x)\n", - x, y, z, *expected_bld, expected_occ->whole & occ_mask, block_occ.whole & occ_mask); + DEBUG(log,out).print("reconciling occupancy at ({}, {}, {}) (bld={}, 0x{:x} ?= 0x{:x})\n", + x, y, z, static_cast(*expected_bld), expected_occ->whole & occ_mask, block_occ.whole & occ_mask); reconcile_map_tile(out, *expected_bld, *expected_occ, block_occ, dry_run, x, y, z); } } } } - INFO(log,out).print("verified %zd buildings, %zd units, %zd items, %zd map blocks, and %zd map tiles\n", + INFO(log,out).print("verified {} buildings, {} units, {} items, {} map blocks, and {} map tiles\n", world->buildings.all.size(), world->units.active.size(), world->items.other.IN_PLAY.size(), world->map.map_blocks.size(), expected.get_size()); } diff --git a/plugins/fixveins.cpp b/plugins/fixveins.cpp index f87701e0bc..1fc003972e 100644 --- a/plugins/fixveins.cpp +++ b/plugins/fixveins.cpp @@ -92,9 +92,9 @@ command_result df_fixveins (color_ostream &out, vector & parameters) } } if (mineral_removed || feature_removed) - out.print("Removed invalid references from %i mineral inclusion and %i map feature tiles.\n", mineral_removed, feature_removed); + out.print("Removed invalid references from {} mineral inclusion and {} map feature tiles.\n", mineral_removed, feature_removed); if (mineral_added || feature_added) - out.print("Restored missing references to %i mineral inclusion and %i map feature tiles.\n", mineral_added, feature_added); + out.print("Restored missing references to {} mineral inclusion and {} map feature tiles.\n", mineral_added, feature_added); return CR_OK; } diff --git a/plugins/flows.cpp b/plugins/flows.cpp index 8ebf968a0b..c13e355b6c 100644 --- a/plugins/flows.cpp +++ b/plugins/flows.cpp @@ -46,11 +46,11 @@ command_result df_flows (color_ostream &out, vector & parameters) } } - out.print("Blocks with liquid_1=true: %d\n", flow1); - out.print("Blocks with liquid_2=true: %d\n", flow2); - out.print("Blocks with both: %d\n", flowboth); - out.print("Water tiles: %d\n", water); - out.print("Magma tiles: %d\n", magma); + out.print("Blocks with liquid_1=true: {}\n", flow1); + out.print("Blocks with liquid_2=true: {}\n", flow2); + out.print("Blocks with both: {}\n", flowboth); + out.print("Water tiles: {}\n", water); + out.print("Magma tiles: {}\n", magma); return CR_OK; } diff --git a/plugins/follow.cpp b/plugins/follow.cpp index 5649d6bdfb..109c37823b 100644 --- a/plugins/follow.cpp +++ b/plugins/follow.cpp @@ -150,7 +150,7 @@ command_result follow (color_ostream &out, std::vector & parameter ss << "Unpause to begin following " << world->raws.creatures.all[followedUnit->race]->name[0]; if (followedUnit->name.has_name) ss << " " << followedUnit->name.first_name; ss << ". Simply manually move the view to break the following.\n"; - out.print("%s", ss.str().c_str()); + out.print("{}", ss.str()); } else followedUnit = 0; is_enabled = (followedUnit != NULL); diff --git a/plugins/forceequip.cpp b/plugins/forceequip.cpp index fa8cb8ef12..290772e87b 100644 --- a/plugins/forceequip.cpp +++ b/plugins/forceequip.cpp @@ -80,7 +80,7 @@ static bool moveToInventory(df::item *item, df::unit *unit, df::body_part_raw * } else if(!item->isClothing() && !item->isArmorNotClothing()) { - if (verbose) { WARN(log).print("Item %d is not clothing or armor; it cannot be equipped. Please choose a different item (or use the Ignore option if you really want to equip an inappropriate item).\n", item->id); } + if (verbose) { WARN(log).print("Item {} is not clothing or armor; it cannot be equipped. Please choose a different item (or use the Ignore option if you really want to equip an inappropriate item).\n", item->id); } return false; } else if (item->getType() != df::enums::item_type::GLOVES && @@ -90,22 +90,22 @@ static bool moveToInventory(df::item *item, df::unit *unit, df::body_part_raw * item->getType() != df::enums::item_type::SHOES && !targetBodyPart) { - if (verbose) { WARN(log).print("Item %d is of an unrecognized type; it cannot be equipped (because the module wouldn't know where to put it).\n", item->id); } + if (verbose) { WARN(log).print("Item {} is of an unrecognized type; it cannot be equipped (because the module wouldn't know where to put it).\n", item->id); } return false; } else if (itemOwner && itemOwner->id != unit->id) { - if (verbose) { WARN(log).print("Item %d is owned by someone else. Equipping it on this unit is not recommended. Please use DFHack's Confiscate plugin, choose a different item, or use the Ignore option to proceed in spite of this warning.\n", item->id); } + if (verbose) { WARN(log).print("Item {} is owned by someone else. Equipping it on this unit is not recommended. Please use DFHack's Confiscate plugin, choose a different item, or use the Ignore option to proceed in spite of this warning.\n", item->id); } return false; } else if (item->flags.bits.in_inventory) { - if (verbose) { WARN(log).print("Item %d is already in a unit's inventory. Direct inventory transfers are not recommended; please move the item to the ground first (or use the Ignore option).\n", item->id); } + if (verbose) { WARN(log).print("Item {} is already in a unit's inventory. Direct inventory transfers are not recommended; please move the item to the ground first (or use the Ignore option).\n", item->id); } return false; } else if (item->flags.bits.in_job) { - if (verbose) { WARN(log).print("Item %d is reserved for use in a queued job. Equipping it is not recommended, as this might interfere with the completion of vital jobs. Use the Ignore option to ignore this warning.\n", item->id); } + if (verbose) { WARN(log).print("Item {} is reserved for use in a queued job. Equipping it is not recommended, as this might interfere with the completion of vital jobs. Use the Ignore option to ignore this warning.\n", item->id); } return false; } @@ -132,45 +132,45 @@ static bool moveToInventory(df::item *item, df::unit *unit, df::body_part_raw * else if (bpIndex < unit->body.body_plan->body_parts.size()) { // The current body part is not the one that was specified in the function call, but we can keep searching - if (verbose) { WARN(log).print("Found bodypart %s; not a match; continuing search.\n", currPart->token.c_str()); } + if (verbose) { WARN(log).print("Found bodypart {}; not a match; continuing search.\n", currPart->token); } continue; } else { // The specified body part has not been found, and we've reached the end of the list. Report failure. - if (verbose) { WARN(log).print("The specified body part (%s) does not belong to the chosen unit. Please double-check to ensure that your spelling is correct, and that you have not chosen a dismembered bodypart.\n",targetBodyPart->token.c_str()); } + if (verbose) { WARN(log).print("The specified body part ({}) does not belong to the chosen unit. Please double-check to ensure that your spelling is correct, and that you have not chosen a dismembered bodypart.\n",targetBodyPart->token); } return false; } - if (verbose) { DEBUG(log).print("Inspecting bodypart %s.\n", currPart->token.c_str()); } + if (verbose) { DEBUG(log).print("Inspecting bodypart {}.\n", currPart->token); } // Inspect the current bodypart if (item->getType() == df::enums::item_type::GLOVES && currPart->flags.is_set(df::body_part_raw_flags::GRASP) && ((item->getGloveHandedness() == const_GloveLeftHandedness && currPart->flags.is_set(df::body_part_raw_flags::LEFT)) || (item->getGloveHandedness() == const_GloveRightHandedness && currPart->flags.is_set(df::body_part_raw_flags::RIGHT)))) { - if (verbose) { DEBUG(log).print("Hand found (%s)...", currPart->token.c_str()); } + if (verbose) { DEBUG(log).print("Hand found ({}).", currPart->token); } } else if (item->getType() == df::enums::item_type::HELM && currPart->flags.is_set(df::body_part_raw_flags::HEAD)) { - if (verbose) { DEBUG(log).print("Head found (%s)...", currPart->token.c_str()); } + if (verbose) { DEBUG(log).print("Head found ({}).", currPart->token); } } else if (item->getType() == df::enums::item_type::ARMOR && currPart->flags.is_set(df::body_part_raw_flags::UPPERBODY)) { - if (verbose) { DEBUG(log).print("Upper body found (%s)...", currPart->token.c_str()); } + if (verbose) { DEBUG(log).print("Upper body found ({}).", currPart->token); } } else if (item->getType() == df::enums::item_type::PANTS && currPart->flags.is_set(df::body_part_raw_flags::LOWERBODY)) { - if (verbose) { DEBUG(log).print("Lower body found (%s)...", currPart->token.c_str()); } + if (verbose) { DEBUG(log).print("Lower body found ({}).", currPart->token); } } else if (item->getType() == df::enums::item_type::SHOES && currPart->flags.is_set(df::body_part_raw_flags::STANCE)) { - if (verbose) { DEBUG(log).print("Foot found (%s)...", currPart->token.c_str()); } + if (verbose) { DEBUG(log).print("Foot found ({}).", currPart->token); } } else if (targetBodyPart && ignoreRestrictions) { // The BP in question would normally be considered ineligible for equipment. But since it was deliberately specified by the user, we'll proceed anyways. - if (verbose) { DEBUG(log).print("Non-standard bodypart found (%s)...", targetBodyPart->token.c_str()); } + if (verbose) { DEBUG(log).print("Non-standard bodypart found ({}).", targetBodyPart->token); } } else if (targetBodyPart) { @@ -205,7 +205,7 @@ static bool moveToInventory(df::item *item, df::unit *unit, df::body_part_raw * // Collision detected; have we reached the limit? if (++collisions >= multiEquipLimit) { - if (verbose) { WARN(log).print(" but it already carries %d piece(s) of equipment. Either remove the existing equipment or use the Multi option.\n", multiEquipLimit); } + if (verbose) { WARN(log).print(" but it already carries {} piece(s) of equipment. Either remove the existing equipment or use the Multi option.\n", multiEquipLimit); } confirmedBodyPart = NULL; break; } @@ -382,13 +382,13 @@ command_result df_forceequip(color_ostream &out, vector & parameters) if (targetBodyPart->token.compare(targetBodyPartCode) == 0) { // It is indeed a match; exit the loop (while leaving the variable populated) - if (verbose) { INFO(log).print("Matching bodypart (%s) found.\n", targetBodyPart->token.c_str()); } + if (verbose) { INFO(log).print("Matching bodypart ({}) found.\n", targetBodyPart->token); } break; } else { // Not a match; nullify the variable (it will get re-populated on the next pass through the loop) - if (verbose) { WARN(log).print("Bodypart \"%s\" does not match \"%s\".\n", targetBodyPart->token.c_str(), targetBodyPartCode.c_str()); } + if (verbose) { WARN(log).print("Bodypart ({}) does not match ({}).\n", targetBodyPart->token, targetBodyPartCode); } targetBodyPart = NULL; } } @@ -396,7 +396,7 @@ command_result df_forceequip(color_ostream &out, vector & parameters) if (!targetBodyPart) { // Loop iteration is complete but no match was found. - WARN(log).print("The unit does not possess a bodypart of type \"%s\". Please check the spelling or choose a different unit.\n", targetBodyPartCode.c_str()); + WARN(log).print("The unit does not possess a bodypart of type ({}). Please check the spelling or choose a different unit.\n", targetBodyPartCode); return CR_FAILURE; } } @@ -466,7 +466,7 @@ command_result df_forceequip(color_ostream &out, vector & parameters) if (itemsEquipped == 0 && !verbose) { WARN(log).print("Some items were found but no equipment changes could be made. Use the /verbose switch to display the reasons for failure.\n"); } - if (itemsEquipped > 0) { INFO(log).print("%d items equipped.\n", itemsEquipped); } + if (itemsEquipped > 0) { INFO(log).print("{} items equipped.\n", itemsEquipped); } // Note: we might expect to recalculate the unit's weight at this point, in order to account for the // added items. In fact, this recalculation occurs automatically during each dwarf's "turn". diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 0ea3227b1b..7c330e0056 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -190,10 +190,10 @@ command_result list_creatures(color_ostream &out, std::vector & pa auto creatureRaw = world->raws.creatures.all[i]; if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) continue; - out.print("%s",creatureRaw->creature_id.c_str()); + out.print("{}",creatureRaw->creature_id); if (detailed) { - out.print("\t%s",creatureRaw->caste[0]->description.c_str()); + out.print("\t{}",creatureRaw->caste[0]->description); } out.print("\n"); } diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 29c6655fc9..53219276bb 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -21,6 +21,8 @@ #include "df/world_object_data.h" #include "df/world_site.h" +#include + using std::string; using std::vector; using std::set; @@ -77,13 +79,13 @@ enum class selectability { // result in the plants not being usable for farming or even collectable at all). selectability selectablePlant(color_ostream& out, const df::plant_raw* plant, bool farming) { - TRACE(log, out).print("analyzing %s\n", plant->id.c_str()); + TRACE(log, out).print("analyzing {}\n", plant->id); const DFHack::MaterialInfo basic_mat = DFHack::MaterialInfo(plant->material_defs.type[plant_material_def::basic_mat], plant->material_defs.idx[plant_material_def::basic_mat]); bool outOfSeason = false; selectability result = selectability::Nonselectable; if (plant->flags.is_set(plant_raw_flags::TREE)) { - DEBUG(log, out).print("%s is a selectable tree\n", plant->id.c_str()); + DEBUG(log, out).print("{} is a selectable tree\n", plant->id); if (farming) { return selectability::Nonselectable; } @@ -92,7 +94,7 @@ selectability selectablePlant(color_ostream& out, const df::plant_raw* plant, bo } } else if (plant->flags.is_set(plant_raw_flags::GRASS)) { - DEBUG(log, out).print("%s is a non selectable Grass\n", plant->id.c_str()); + DEBUG(log, out).print("{} is a non selectable Grass\n", plant->id); return selectability::Grass; } @@ -104,7 +106,7 @@ selectability selectablePlant(color_ostream& out, const df::plant_raw* plant, bo (basic_mat.material->flags.is_set(material_flags::EDIBLE_RAW) || basic_mat.material->flags.is_set(material_flags::EDIBLE_COOKED))) { - DEBUG(log, out).print("%s is edible\n", plant->id.c_str()); + DEBUG(log, out).print("{} is edible\n", plant->id); if (farming) { if (basic_mat.material->flags.is_set(material_flags::EDIBLE_RAW)) { result = selectability::Selectable; @@ -120,7 +122,7 @@ selectability selectablePlant(color_ostream& out, const df::plant_raw* plant, bo plant->flags.is_set(plant_raw_flags::EXTRACT_VIAL) || plant->flags.is_set(plant_raw_flags::EXTRACT_BARREL) || plant->flags.is_set(plant_raw_flags::EXTRACT_STILL_VIAL)) { - DEBUG(log, out).print("%s is thread/mill/extract\n", plant->id.c_str()); + DEBUG(log, out).print("{} is thread/mill/extract\n", plant->id); if (farming) { result = selectability::Selectable; } @@ -133,7 +135,7 @@ selectability selectablePlant(color_ostream& out, const df::plant_raw* plant, bo (basic_mat.material->reaction_product.id.size() > 0 || basic_mat.material->reaction_class.size() > 0)) { - DEBUG(log, out).print("%s has a reaction\n", plant->id.c_str()); + DEBUG(log, out).print("{} has a reaction\n", plant->id); if (farming) { result = selectability::Selectable; } @@ -168,7 +170,7 @@ selectability selectablePlant(color_ostream& out, const df::plant_raw* plant, bo if (*cur_year_tick >= plant->growths[i]->timing_1 && (plant->growths[i]->timing_2 == -1 || *cur_year_tick <= plant->growths[i]->timing_2)) { - DEBUG(log, out).print("%s has an edible seed or a stockpile growth\n", plant->id.c_str()); + DEBUG(log, out).print("{} has an edible seed or a stockpile growth\n", plant->id); if (!farming || seedSource) { return selectability::Selectable; } @@ -202,11 +204,11 @@ selectability selectablePlant(color_ostream& out, const df::plant_raw* plant, bo } if (outOfSeason) { - DEBUG(log, out).print("%s has an out of season growth\n", plant->id.c_str()); + DEBUG(log, out).print("{} has an out of season growth\n", plant->id); return selectability::OutOfSeason; } else { - DEBUG(log, out).print("%s cannot be gathered\n", plant->id.c_str()); + DEBUG(log, out).print("{} cannot be gathered\n", plant->id); return result; } } @@ -245,7 +247,7 @@ bool picked(const df::plant* plant, int32_t growth_subtype, int32_t growth_densi } bool designate(color_ostream& out, const df::plant* plant, bool farming) { - TRACE(log, out).print("Attempting to designate %s at (%i, %i, %i)\n", world->raws.plants.all[plant->material]->id.c_str(), plant->pos.x, plant->pos.y, plant->pos.z); + TRACE(log, out).print("Attempting to designate {} at ({}, {}, {})\n", world->raws.plants.all[plant->material]->id, plant->pos.x, plant->pos.y, plant->pos.z); if (!farming) { bool istree = (tileMaterial(Maps::getTileBlock(plant->pos)->tiletype[plant->pos.x % 16][plant->pos.y % 16]) == tiletype_material::TREE); @@ -279,14 +281,14 @@ bool designate(color_ostream& out, const df::plant* plant, bool farming) { } for (size_t i = 0; i < plant_raw->growths.size(); i++) { - TRACE(log, out).print("growth item type=%d\n", plant_raw->growths[i]->item_type); + TRACE(log, out).print("growth item type={}\n", ENUM_AS_STR(plant_raw->growths[i]->item_type)); // Only trees have seed growths in vanilla, but raws can be modded... if (plant_raw->growths[i]->item_type != df::item_type::SEEDS && plant_raw->growths[i]->item_type != df::item_type::PLANT_GROWTH) continue; const DFHack::MaterialInfo growth_mat = DFHack::MaterialInfo(plant_raw->growths[i]->mat_type, plant_raw->growths[i]->mat_index); - TRACE(log, out).print("edible_cooked=%d edible_raw=%d leaf_mat=%d\n", + TRACE(log, out).print("edible_cooked={} edible_raw={} leaf_mat={}\n", growth_mat.material->flags.is_set(material_flags::EDIBLE_COOKED), growth_mat.material->flags.is_set(material_flags::EDIBLE_RAW), growth_mat.material->flags.is_set(material_flags::STOCKPILE_PLANT_GROWTH)); @@ -400,20 +402,20 @@ command_result df_getplants(color_ostream& out, vector & parameters) { plantSelections[i] = selectablePlant(out, plant, farming); switch (plantSelections[i]) { case selectability::Grass: - out.printerr("%s is a grass and cannot be gathered\n", plant->id.c_str()); + out.printerr("{} is a grass and cannot be gathered\n", plant->id); break; case selectability::Nonselectable: if (farming) { - out.printerr("%s does not have any parts that can be gathered for seeds for farming\n", plant->id.c_str()); + out.printerr("{} does not have any parts that can be gathered for seeds for farming\n", plant->id); } else { - out.printerr("%s does not have any parts that can be gathered\n", plant->id.c_str()); + out.printerr("{} does not have any parts that can be gathered\n", plant->id); } break; case selectability::OutOfSeason: - out.printerr("%s is out of season, with nothing that can be gathered now\n", plant->id.c_str()); + out.printerr("{} is out of season, with nothing that can be gathered now\n", plant->id); break; case selectability::Selectable: @@ -427,7 +429,7 @@ command_result df_getplants(color_ostream& out, vector & parameters) { if (plantNames.size() > 0) { out.printerr("Invalid plant ID(s):"); for (set::const_iterator it = plantNames.begin(); it != plantNames.end(); it++) - out.printerr(" %s", it->c_str()); + out.printerr(" {}", *it); out.printerr("\n"); return CR_FAILURE; } @@ -452,7 +454,7 @@ command_result df_getplants(color_ostream& out, vector & parameters) { case selectability::OutOfSeason: { if (!treesonly) { - out.print("* (shrub) %s - %s is out of season\n", plant->id.c_str(), plant->name.c_str()); + out.print("* (shrub) {} - {} is out of season\n", plant->id, plant->name); } break; } @@ -463,7 +465,7 @@ command_result df_getplants(color_ostream& out, vector & parameters) { (shrubsonly && !plant->flags.is_set(plant_raw_flags::TREE)) || (!treesonly && !shrubsonly)) // 'farming' weeds out trees when determining selectability, so no need to test that explicitly { - out.print("* (%s) %s - %s\n", plant->flags.is_set(plant_raw_flags::TREE) ? "tree" : "shrub", plant->id.c_str(), plant->name.c_str()); + out.print("* ({}) {} - {}\n", plant->flags.is_set(plant_raw_flags::TREE) ? "tree" : "shrub", plant->id, plant->name); } break; } @@ -476,52 +478,63 @@ command_result df_getplants(color_ostream& out, vector & parameters) { } count = 0; - for (size_t i = 0; i < world->plants.all.size(); i++) { - const df::plant* plant = world->plants.all[i]; + for (auto* plant : world->plants.all) + { df::map_block* cur = Maps::getTileBlock(plant->pos); - TRACE(log, out).print("Examining %s at (%i, %i, %i) [index=%d]\n", world->raws.plants.all[plant->material]->id.c_str(), plant->pos.x, plant->pos.y, plant->pos.z, (int)i); + auto mat = plant->material; + if (mat < 0 || mat >= int16_t(world->raws.plants.all.size())) + { + WARN(log, out).print("plant with invalid material {} in plant vector", mat); + continue; + } + + TRACE(log, out).print("Examining {} at ({}, {}, {})\n", world->raws.plants.all[mat]->id, plant->pos.x, plant->pos.y, plant->pos.z); int x = plant->pos.x % 16; int y = plant->pos.y % 16; - if (plantSelections[plant->material] == selectability::OutOfSeason || - plantSelections[plant->material] == selectability::Selectable) { + if (plantSelections[mat] == selectability::OutOfSeason || + plantSelections[mat] == selectability::Selectable) + { if (exclude || - plantSelections[plant->material] == selectability::OutOfSeason) + plantSelections[mat] == selectability::OutOfSeason) continue; } - else { + else + { if (!exclude) continue; } df::tiletype tt = cur->tiletype[x][y]; - df::tiletype_material mat = tileMaterial(tt); + df::tiletype_material tile_mat = tileMaterial(tt); if ((treesonly || tt != tiletype::Shrub) && ENUM_ATTR(plant_type, is_shrub, plant->type)) continue; - if ((shrubsonly || mat != tiletype_material::TREE) && !ENUM_ATTR(plant_type, is_shrub, plant->type)) + if ((shrubsonly || tile_mat != tiletype_material::TREE) && !ENUM_ATTR(plant_type, is_shrub, plant->type)) continue; if (cur->designation[x][y].bits.hidden) continue; - if (collectionCount[plant->material] >= maxCount) + if (collectionCount[mat] >= maxCount) continue; - if (deselect && Designations::unmarkPlant(plant)) { - collectionCount[plant->material]++; + if (deselect && Designations::unmarkPlant(plant)) + { + collectionCount[mat]++; ++count; } - if (!deselect && designate(out, plant, farming)) { - DEBUG(log, out).print("Designated %s at (%i, %i, %i), %d\n", world->raws.plants.all[plant->material]->id.c_str(), plant->pos.x, plant->pos.y, plant->pos.z, (int)i); - collectionCount[plant->material]++; + if (!deselect && designate(out, plant, farming)) + { + DEBUG(log, out).print("Designated {} at ({}, {}, {})\n", world->raws.plants.all[mat]->id, plant->pos.x, plant->pos.y, plant->pos.z); + collectionCount[mat]++; ++count; } } if (count && verbose) { for (size_t i = 0; i < plantSelections.size(); i++) { if (collectionCount[i] > 0) - out.print("Updated %d %s designations.\n", (int)collectionCount[i], world->raws.plants.all[i]->id.c_str()); + out.print("Updated {} {} designations.\n", collectionCount[i], world->raws.plants.all[i]->id); } out.print("\n"); } - out.print("Updated %d plant designations.\n", (int)count); + out.print("Updated {} plant designations.\n", count); return CR_OK; } diff --git a/plugins/hotkeys.cpp b/plugins/hotkeys.cpp index 13988a21f1..e692043868 100644 --- a/plugins/hotkeys.cpp +++ b/plugins/hotkeys.cpp @@ -2,8 +2,10 @@ #include #include +#include "modules/DFSDL.h" #include "modules/Gui.h" #include "modules/Screen.h" +#include "modules/Hotkey.h" #include "Debug.h" #include "LuaTools.h" @@ -40,11 +42,11 @@ static bool can_invoke(const string &cmdline, df::viewscreen *screen) { } static int cleanupHotkeys(lua_State *) { - DEBUG(log).print("cleaning up old stub keybindings for: %s\n", join_strings(", ", Gui::getCurFocus(true)).c_str()); + DEBUG(log).print("cleaning up old stub keybindings for: {}\n", join_strings(", ", Gui::getCurFocus(true))); std::for_each(sorted_keys.begin(), sorted_keys.end(), [](const string &sym) { string keyspec = sym + "@" + MENU_SCREEN_FOCUS_STRING; - DEBUG(log).print("clearing keybinding: %s\n", keyspec.c_str()); - Core::getInstance().ClearKeyBindings(keyspec); + DEBUG(log).print("clearing keybinding: {}\n", keyspec); + Core::getInstance().getHotkeyManager()->removeKeybind(keyspec); }); valid = false; sorted_keys.clear(); @@ -82,61 +84,18 @@ static void add_binding_if_valid(color_ostream &out, const string &sym, const st sorted_keys.push_back(sym); string keyspec = sym + "@" + MENU_SCREEN_FOCUS_STRING; string binding = "hotkeys invoke " + int_to_string(sorted_keys.size() - 1); - DEBUG(log).print("adding keybinding: %s -> %s\n", keyspec.c_str(), binding.c_str()); - Core::getInstance().AddKeyBinding(keyspec, binding); + DEBUG(log).print("adding keybinding: {} -> {}\n", keyspec, binding); + Core::getInstance().getHotkeyManager()->addKeybind(keyspec, binding); } static void find_active_keybindings(color_ostream &out, df::viewscreen *screen, bool filtermenu) { - DEBUG(log).print("scanning for active keybindings\n"); if (valid) cleanupHotkeys(NULL); - vector valid_keys; - - for (char c = '0'; c <= '9'; c++) { - valid_keys.push_back(string(&c, 1)); - } - - for (char c = 'A'; c <= 'Z'; c++) { - valid_keys.push_back(string(&c, 1)); - } - - for (int i = 1; i <= 12; i++) { - valid_keys.push_back('F' + int_to_string(i)); - } - - valid_keys.push_back("`"); - - for (int shifted = 0; shifted < 2; shifted++) { - for (int alt = 0; alt < 2; alt++) { - for (int ctrl = 0; ctrl < 2; ctrl++) { - for (auto it = valid_keys.begin(); it != valid_keys.end(); it++) { - string sym; - if (ctrl) sym += "Ctrl-"; - if (alt) sym += "Alt-"; - if (shifted) sym += "Shift-"; - sym += *it; - - auto list = Core::getInstance().ListKeyBindings(sym); - for (auto invoke_cmd = list.begin(); invoke_cmd != list.end(); invoke_cmd++) { - string::size_type colon_pos = invoke_cmd->find(":"); - // colons at location 0 are for commands like ":lua" - if (colon_pos == string::npos || colon_pos == 0) { - add_binding_if_valid(out, sym, *invoke_cmd, screen, filtermenu); - } - else { - vector tokens; - split_string(&tokens, *invoke_cmd, ":"); - string focus = tokens[0].substr(1); - if(Gui::matchFocusString(focus)) { - auto cmdline = trim(tokens[1]); - add_binding_if_valid(out, sym, cmdline, screen, filtermenu); - } - } - } - } - } - } + auto active_binds = Core::getInstance().getHotkeyManager()->listActiveKeybinds(); + for (const auto& bind : active_binds) { + string sym = bind.spec.toString(false); + add_binding_if_valid(out, sym, bind.cmdline, screen, filtermenu); } valid = true; @@ -164,10 +123,10 @@ static void list(color_ostream &out) { if (!valid) find_active_keybindings(out, Gui::getCurViewscreen(true), false); - out.print("Valid keybindings for the current focus:\n %s\n", - join_strings("\n", Gui::getCurFocus(true)).c_str()); + out.print("Valid keybindings for the current focus:\n {}\n", + join_strings("\n", Gui::getCurFocus(true))); std::for_each(sorted_keys.begin(), sorted_keys.end(), [&](const string &sym) { - out.print("%s: %s\n", sym.c_str(), current_bindings[sym].c_str()); + out.print("{}: {}\n", sym, current_bindings[sym]); }); if (!was_valid) @@ -181,7 +140,7 @@ static bool invoke_command(color_ostream &out, const size_t index) { return false; auto cmd = current_bindings[sorted_keys[index]]; - DEBUG(log).print("invoking command: '%s'\n", cmd.c_str()); + DEBUG(log).print("invoking command: '{}'\n", cmd); { Screen::Hide hideGuard(screen, Screen::Hide::RESTORE_AT_TOP); @@ -194,11 +153,11 @@ static bool invoke_command(color_ostream &out, const size_t index) { static command_result hotkeys_cmd(color_ostream &out, vector & parameters) { if (!parameters.size()) { - DEBUG(log).print("invoking command: '%s'\n", INVOKE_MENU_DEFAULT_COMMAND.c_str()); + DEBUG(log).print("invoking command: '{}'\n", INVOKE_MENU_DEFAULT_COMMAND); return Core::getInstance().runCommand(out, INVOKE_MENU_DEFAULT_COMMAND); } else if (parameters.size() == 2 && parameters[0] == "menu") { string cmd = INVOKE_MENU_BASE_COMMAND + parameters[1]; - DEBUG(log).print("invoking command: '%s'\n", cmd.c_str()); + DEBUG(log).print("invoking command: '{}'\n", cmd); return Core::getInstance().runCommand(out, cmd); } diff --git a/plugins/infinite-sky.cpp b/plugins/infinite-sky.cpp index ffa62ed830..50c0563ef3 100644 --- a/plugins/infinite-sky.cpp +++ b/plugins/infinite-sky.cpp @@ -10,8 +10,13 @@ #include "df/block_column_print_infost.h" #include "df/construction.h" +#include "df/entity_plot_invasion_mapst.h" +#include "df/historical_entity.h" +#include "df/invasion_info.h" #include "df/map_block.h" #include "df/map_block_column.h" +#include "df/plotinfost.h" +#include "df/plot_invasion_mapst.h" #include "df/world.h" #include "df/z_level_flags.h" @@ -28,6 +33,7 @@ using namespace df::enums; DFHACK_PLUGIN("infinite-sky"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(plotinfo); REQUIRE_GLOBAL(world); namespace DFHack { @@ -62,13 +68,13 @@ void cleanup() { DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } if (enable != is_enabled) { is_enabled = enable; DEBUG(control, out) - .print("%s from the API; persisting\n", + .print("{} from the API; persisting\n", is_enabled ? "enabled" : "disabled"); config.set_bool(CONFIG_IS_ENABLED, is_enabled); @@ -80,7 +86,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { } } else { DEBUG(control, out) - .print("%s from the API, but already %s; no action\n", + .print("{} from the API, but already {}; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); } @@ -103,7 +109,7 @@ DFhackCExport command_result plugin_load_site_data(color_ostream &out) { plugin_enable(out, true); } DEBUG(control, out) - .print("loading persisted enabled state: %s\n", + .print("loading persisted enabled state: {}\n", is_enabled ? "true" : "false"); return CR_OK; } @@ -113,7 +119,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, if (event == DFHack::SC_WORLD_UNLOADED) { if (is_enabled) { DEBUG(control, out) - .print("world unloaded; disabling %s\n", plugin_name); + .print("world unloaded; disabling {}\n", plugin_name); is_enabled = false; cleanup(); } @@ -130,97 +136,10 @@ static void constructionEventHandler(color_ostream &out, void *ptr) { doInfiniteSky(out, 1); } -void doInfiniteSky(color_ostream& out, int32_t howMany) { - int32_t z_count_block = world->map.z_count_block; - df::map_block ****block_index = world->map.block_index; - - cuboid last_air_layer( - 0, 0, world->map.z_count_block - 1, - world->map.x_count_block - 1, world->map.y_count_block - 1, world->map.z_count_block - 1); - - last_air_layer.forCoord([&](df::coord bpos) { - // Allocate a new block column and copy over data from the old - df::map_block **blockColumn = - new df::map_block *[z_count_block + howMany]; - memcpy(blockColumn, block_index[bpos.x][bpos.y], - z_count_block * sizeof(df::map_block *)); - delete[] block_index[bpos.x][bpos.y]; - block_index[bpos.x][bpos.y] = blockColumn; - - df::map_block *last_air_block = blockColumn[bpos.z]; - for (int32_t count = 0; count < howMany; count++) { - df::map_block *air_block = new df::map_block(); - std::fill(&air_block->tiletype[0][0], - &air_block->tiletype[0][0] + (16 * 16), - df::tiletype::OpenSpace); - - // Set block positions properly (based on prior air layer) - air_block->map_pos = last_air_block->map_pos; - air_block->map_pos.z += count + 1; - air_block->region_pos = last_air_block->region_pos; - - // Copy other potentially important metadata from prior air - // layer - std::memcpy(air_block->lighting, last_air_block->lighting, - sizeof(air_block->lighting)); - std::memcpy(air_block->temperature_1, last_air_block->temperature_1, - sizeof(air_block->temperature_1)); - std::memcpy(air_block->temperature_2, last_air_block->temperature_2, - sizeof(air_block->temperature_2)); - std::memcpy(air_block->region_offset, last_air_block->region_offset, - sizeof(air_block->region_offset)); - - // Create tile designations to inform lighting and - // outside markers - df::tile_designation designation{}; - designation.bits.light = true; - designation.bits.outside = true; - std::fill(&air_block->designation[0][0], - &air_block->designation[0][0] + (16 * 16), designation); - - blockColumn[z_count_block + count] = air_block; - world->map.map_blocks.push_back(air_block); - - // deal with map_block_column stuff even though it'd probably be - // fine - df::map_block_column *column = - world->map.column_index[bpos.x][bpos.y]; - if (!column) { - DEBUG(cycle, out) - .print("%s, line %d: column is null (%d, %d).\n", __FILE__, - __LINE__, bpos.x, bpos.y); - continue; - } - df::block_column_print_infost *glyphs = new df::block_column_print_infost; - glyphs->x[0] = 0; - glyphs->x[1] = 1; - glyphs->x[2] = 2; - glyphs->x[3] = 3; - glyphs->y[0] = 0; - glyphs->y[1] = 0; - glyphs->y[2] = 0; - glyphs->y[3] = 0; - glyphs->tile[0] = 'e'; - glyphs->tile[1] = 'x'; - glyphs->tile[2] = 'p'; - glyphs->tile[3] = '^'; - column->unmined_glyphs.push_back(glyphs); - } - return true; - }); - - // Update global z level flags - df::z_level_flags *flags = new df::z_level_flags[z_count_block + howMany]; - memcpy(flags, world->map_extras.z_level_flags, - z_count_block * sizeof(df::z_level_flags)); - for (int32_t count = 0; count < howMany; count++) { - flags[z_count_block + count].whole = 0; - flags[z_count_block + count].bits.update = 1; - } - world->map.z_count_block += howMany; - world->map.z_count += howMany; - delete[] world->map_extras.z_level_flags; - world->map_extras.z_level_flags = flags; + +void doInfiniteSky(color_ostream& out, int32_t quantity) +{ + Maps::addBlockColumns(world->map.z_count_block + quantity); } struct infinitesky_options { @@ -242,7 +161,7 @@ struct_identity infinitesky_options::_identity{sizeof(infinitesky_options), &df: command_result infiniteSky(color_ostream &out, std::vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -254,11 +173,11 @@ command_result infiniteSky(color_ostream &out, return CR_WRONG_USAGE; if (opts.n > 0) { - out.print("Infinite-sky: creating %d new z-level%s of sky.\n", opts.n, + out.print("Infinite-sky: creating {} new z-level{} of sky.\n", opts.n, opts.n == 1 ? "" : "s"); doInfiniteSky(out, opts.n); } else { - out.print("Construction monitoring is %s.\n", + out.print("Construction monitoring is {}.\n", is_enabled ? "enabled" : "disabled"); } return CR_OK; diff --git a/plugins/jobutils.cpp b/plugins/jobutils.cpp index 37912d8429..c32b9adf22 100644 --- a/plugins/jobutils.cpp +++ b/plugins/jobutils.cpp @@ -100,8 +100,8 @@ static command_result job_material_in_job(color_ostream &out, MaterialInfo &new_ if (!new_mat.isValid() || new_mat.type != 0) { - out.printerr("New job material isn't inorganic: %s\n", - new_mat.toString().c_str()); + out.printerr("New job material isn't inorganic: {}\n", + new_mat.toString()); return CR_FAILURE; } @@ -109,22 +109,22 @@ static command_result job_material_in_job(color_ostream &out, MaterialInfo &new_ if (!cur_mat.isValid() || cur_mat.type != 0) { - out.printerr("Current job material isn't inorganic: %s\n", - cur_mat.toString().c_str()); + out.printerr("Current job material isn't inorganic: {}\n", + cur_mat.toString()); return CR_FAILURE; } df::craft_material_class old_class = cur_mat.getCraftClass(); if (old_class == craft_material_class::None) { - out.printerr("Unexpected current material type: %s\n", - cur_mat.toString().c_str()); + out.printerr("Unexpected current material type: {}\n", + cur_mat.toString()); return CR_FAILURE; } if (new_mat.getCraftClass() != old_class) { - out.printerr("New material %s does not satisfy requirement: %s\n", - new_mat.toString().c_str(), ENUM_KEY_STR(craft_material_class, old_class).c_str()); + out.printerr("New material {} does not satisfy requirement: {}\n", + new_mat.toString(), ENUM_KEY_STR(craft_material_class, old_class)); return CR_FAILURE; } @@ -135,15 +135,15 @@ static command_result job_material_in_job(color_ostream &out, MaterialInfo &new_ if (item_mat != cur_mat) { - out.printerr("Job item %zu has different material: %s\n", - i, item_mat.toString().c_str()); + out.printerr("Job item {} has different material: {}\n", + i, item_mat.toString()); return CR_FAILURE; } if (!new_mat.matches(*item)) { - out.printerr("Job item %zu requirements not satisfied by %s.\n", - i, new_mat.toString().c_str()); + out.printerr("Job item {} requirements not satisfied by {}.\n", + i, new_mat.toString()); return CR_FAILURE; } } @@ -212,7 +212,7 @@ static command_result job_material_in_build(color_ostream &out, MaterialInfo &ne } } - out.printerr("Could not find material in list: %s\n", new_mat.toString().c_str()); + out.printerr("Could not find material in list: {}\n", new_mat.toString()); return CR_FAILURE; } @@ -222,7 +222,7 @@ static command_result job_material(color_ostream &out, vector & paramet if (parameters.size() == 1) { if (!new_mat.find(parameters[0])) { - out.printerr("Could not find material: %s\n", parameters[0].c_str()); + out.printerr("Could not find material: {}\n", parameters[0]); return CR_WRONG_USAGE; } } @@ -253,7 +253,7 @@ static command_result job_duplicate(color_ostream &out, vector & parame job->job_type != job_type::CollectSand && job->job_type != job_type::CollectClay)) { - out.printerr("Cannot duplicate job %s\n", ENUM_KEY_STR(job_type,job->job_type).c_str()); + out.printerr("Cannot duplicate job {}\n", ENUM_KEY_STR(job_type,job->job_type)); return CR_FAILURE; } diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index a814bbd5b0..0b5526d996 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -58,7 +58,8 @@ using namespace df::enums; DFHACK_PLUGIN("liquids"); REQUIRE_GLOBAL(world); -static const char * HISTORY_FILE = "dfhack-config/liquids.history"; +auto constexpr HISTORY_FILE = "liquids.history"; + CommandHistory liquids_hist; command_result df_liquids (color_ostream &out, vector & parameters); @@ -66,7 +67,7 @@ command_result df_liquids_here (color_ostream &out, vector & parameters DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - liquids_hist.load(HISTORY_FILE); + liquids_hist.load(DFHack::Core::getInstance().getConfigPath() / HISTORY_FILE); commands.push_back(PluginCommand( "liquids", "Place magma, water or obsidian.", @@ -82,7 +83,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - DEBUG(control, out).print("initializing %s\n", plugin_name); + DEBUG(control, out).print("initializing {}\n", plugin_name); commands.push_back(PluginCommand( plugin_name, @@ -117,7 +117,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -445,12 +445,12 @@ static const struct BadFlags { } bad_flags; static void scan_item(color_ostream &out, df::item *item, StockProcessor &processor) { - DEBUG(cycle,out).print("scan_item [%s] item_id=%d\n", processor.name.c_str(), item->id); + DEBUG(cycle,out).print("scan_item [{}] item_id={}\n", processor.name, item->id); if (DBG_NAME(cycle).isEnabled(DebugCategory::LTRACE)) { string name = ""; item->getItemDescription(&name, 0); - TRACE(cycle,out).print("item: %s\n", name.c_str()); + TRACE(cycle,out).print("item: {}\n", name); } if (processor.is_designated(out, item)) { @@ -539,7 +539,7 @@ static void train_partials(color_ostream& out, int32_t& train_count) { continue; if (Units::assignTrainer(unit)) { - DEBUG(cycle,out).print("assigned trainer to unit %d\n", unit->id); + DEBUG(cycle,out).print("assigned trainer to unit {}\n", unit->id); ++train_count; } } @@ -549,7 +549,7 @@ static void do_cycle(color_ostream& out, int32_t& melt_count, int32_t& trade_count, int32_t& dump_count, int32_t& train_count, int32_t& forbid_count, int32_t& claim_count) { - DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + DEBUG(cycle,out).print("running {} cycle\n", plugin_name); cycle_timestamp = world->frame_counter; ProcessorStats melt_stats, trade_stats, dump_stats, train_stats, forbid_stats, claim_stats; @@ -593,7 +593,7 @@ static void do_cycle(color_ostream& out, train_partials(out, train_count); } - TRACE(cycle,out).print("exit %s do_cycle\n", plugin_name); + TRACE(cycle,out).print("exit {} do_cycle\n", plugin_name); } ///////////////////////////////////////////////////// @@ -673,21 +673,21 @@ static int logistics_getStockpileData(lua_State *L) { } static void logistics_cycle(color_ostream &out, bool quiet = false) { - DEBUG(control, out).print("entering logistics_cycle%s\n", quiet ? " (quiet)" : ""); + DEBUG(control, out).print("entering logistics_cycle{}\n", quiet ? " (quiet)" : ""); int32_t melt_count = 0, trade_count = 0, dump_count = 0, train_count = 0, forbid_count = 0, claim_count = 0; do_cycle(out, melt_count, trade_count, dump_count, train_count, forbid_count, claim_count); if (0 < melt_count || !quiet) - out.print("logistics: designated %d item%s for melting\n", melt_count, (melt_count == 1) ? "" : "s"); + out.print("logistics: designated {} item{} for melting\n", melt_count, (melt_count == 1) ? "" : "s"); if (0 < trade_count || !quiet) - out.print("logistics: designated %d item%s for trading\n", trade_count, (trade_count == 1) ? "" : "s"); + out.print("logistics: designated {} item{} for trading\n", trade_count, (trade_count == 1) ? "" : "s"); if (0 < dump_count || !quiet) - out.print("logistics: designated %d item%s for dumping\n", dump_count, (dump_count == 1) ? "" : "s"); + out.print("logistics: designated {} item{} for dumping\n", dump_count, (dump_count == 1) ? "" : "s"); if (0 < train_count || !quiet) - out.print("logistics: designated %d animal%s for training\n", train_count, (train_count == 1) ? "" : "s"); + out.print("logistics: designated {} animal{} for training\n", train_count, (train_count == 1) ? "" : "s"); if (0 < forbid_count || !quiet) - out.print("logistics: designated %d item%s forbidden\n", forbid_count, (forbid_count == 1) ? "" : "s"); + out.print("logistics: designated {} item{} forbidden\n", forbid_count, (forbid_count == 1) ? "" : "s"); if (0 < claim_count || !quiet) - out.print("logistics: claimed %d forbidden item%s\n", claim_count, (claim_count == 1) ? "" : "s"); + out.print("logistics: claimed {} forbidden item{} \n", claim_count, (claim_count == 1) ? "" : "s"); } static void find_stockpiles(lua_State *L, int idx, @@ -756,11 +756,11 @@ static void logistics_setStockpileConfig(color_ostream& out, int stockpile_numbe bool dump, bool train, int forbid, bool melt_masterworks) { DEBUG(control, out).print("entering logistics_setStockpileConfig\n"); - DEBUG(control, out).print("stockpile_number=%d, melt=%d, trade=%d, dump=%d, train=%d, forbid=%d, melt_masterworks=%d\n", + DEBUG(control, out).print("stockpile_number={}, melt={}, trade={}, dump={}, train={}, forbid={}, melt_masterworks={}\n", stockpile_number, melt, trade, dump, train, forbid, melt_masterworks); if (!find_stockpile(stockpile_number)) { - out.printerr("invalid stockpile number: %d\n", stockpile_number); + out.printerr("invalid stockpile number: {}\n", stockpile_number); return; } @@ -843,8 +843,8 @@ static int logistics_getGlobalCounts(lua_State *L) { } static bool logistics_setFeature(color_ostream &out, bool enabled, string feature) { - DEBUG(control, out).print("entering logistics_setFeature (enabled=%d, feature=%s)\n", - enabled, feature.c_str()); + DEBUG(control, out).print("entering logistics_setFeature (enabled={}, feature={})\n", + enabled, feature); if (feature != "autoretrain") return false; config.set_bool(CONFIG_TRAIN_PARTIAL, enabled); @@ -854,7 +854,7 @@ static bool logistics_setFeature(color_ostream &out, bool enabled, string featur } static bool logistics_getFeature(color_ostream &out, string feature) { - DEBUG(control, out).print("entering logistics_getFeature (feature=%s)\n", feature.c_str()); + DEBUG(control, out).print("entering logistics_getFeature (feature={})\n", feature); if (feature != "autoretrain") return false; return config.get_bool(CONFIG_TRAIN_PARTIAL); diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua index 8c765d563f..58e7421c51 100644 --- a/plugins/lua/blueprint.lua +++ b/plugins/lua/blueprint.lua @@ -204,7 +204,7 @@ end -- returns the name of the output file for the given context function get_filename(opts, phase, ordinal) - local fullname = 'dfhack-config/blueprints/' .. opts.name + local fullname = dfhack.getConfigPath() .. '/blueprints/' .. opts.name local _,_,basename = opts.name:find('([^/]+)/*$') if not basename then -- should not happen since opts.name should already be validated diff --git a/plugins/lua/buildingplan/planneroverlay.lua b/plugins/lua/buildingplan/planneroverlay.lua index f92dc95d84..4d7180afed 100644 --- a/plugins/lua/buildingplan/planneroverlay.lua +++ b/plugins/lua/buildingplan/planneroverlay.lua @@ -11,10 +11,37 @@ local utils = require('utils') local widgets = require('gui.widgets') require('dfhack.buildings') -config = config or json.open('dfhack-config/buildingplan.json') +config = config or json.open(dfhack.getConfigPath() .. '/buildingplan.json') local uibs = df.global.buildreq +local ITEM_TO_JOB = { + [df.item_type.BED] = 'ConstructBed', [df.item_type.DOOR] = 'ConstructDoor', + [df.item_type.CABINET] = 'ConstructCabinet', [df.item_type.TABLE] = 'ConstructTable', + [df.item_type.CHAIR] = 'ConstructThrone', [df.item_type.BOX] = 'ConstructChest', + [df.item_type.ARMORSTAND] = 'ConstructArmorStand', [df.item_type.WEAPONRACK] = 'ConstructWeaponRack', + [df.item_type.STATUE] = 'ConstructStatue', [df.item_type.COFFIN] = 'ConstructCoffin', + [df.item_type.HATCH_COVER] = 'ConstructHatchCover', [df.item_type.GRATE] = 'ConstructGrate', + [df.item_type.QUERN] = 'ConstructQuern', [df.item_type.MILLSTONE] = 'ConstructMillstone', + [df.item_type.TRACTION_BENCH] = 'ConstructTractionBench', [df.item_type.SLAB] = 'ConstructSlab', + [df.item_type.ANVIL] = 'ForgeAnvil', [df.item_type.WINDOW] = 'MakeWindow', + [df.item_type.CAGE] = 'MakeCage', [df.item_type.BARREL] = 'MakeBarrel', + [df.item_type.BUCKET] = 'MakeBucket', [df.item_type.ANIMALTRAP] = 'MakeAnimalTrap', + [df.item_type.CHAIN] = 'MakeChain', [df.item_type.FLASK] = 'MakeFlask', + [df.item_type.GOBLET] = 'MakeGoblet', [df.item_type.BLOCKS] = 'ConstructBlocks', +} + +local JOB_DEFAULTS = { + ConstructBed='wood', ConstructTractionBench='wood', MakeCage='wood', + MakeBarrel='wood', MakeBucket='wood', MakeAnimalTrap='wood', + ForgeAnvil='iron', MakeChain='iron', MakeFlask='iron', MakeWindow='glass' +} + +local VALID_MAT_CATS = { + wood=true, bone=true, shell=true, horn=true, pearl=true, tooth=true, + leather=true, silk=true, yarn=true, cloth=true, plant=true +} + reset_counts_flag = false editing_filters_flag = false @@ -119,6 +146,10 @@ local function is_construction() return uibs.building_type == df.building_type.Construction end +local function is_siege_engine() + return uibs.building_type == df.building_type.SiegeEngine +end + local function tile_is_construction(pos) local tt = dfhack.maps.getTileType(pos) if not tt then return false end @@ -144,7 +175,7 @@ local function is_interior(bounds, x, y) y ~= bounds.y1 and y ~= bounds.y2 end --- adjusted from CycleHotkeyLabel on the planner panel +-- adjusted from WeaponSpiketrapPanel (HotKey & Slider) on the planner panel local weapon_quantity = 1 local function get_quantity(filter, hollow, bounds) @@ -235,6 +266,7 @@ local direction_panel_types = utils.invert{ df.building_type.WaterWheel, df.building_type.AxleHorizontal, df.building_type.Rollers, + df.building_type.SiegeEngine, } local function has_direction_panel() @@ -243,7 +275,7 @@ local function has_direction_panel() and uibs.building_subtype == df.trap_type.TrackStop) end -local pressure_plate_panel_frame = {t=4, h=37, w=46, r=28} +local pressure_plate_panel_frame = {t=4, h=38, w=50, r=28} local function has_pressure_plate_panel() return is_pressure_plate() @@ -405,6 +437,8 @@ function ItemLine:get_item_line_text() end self.note = string.char(192) .. self.note -- character 192 is "â””" + self.quantity = quantity + return ('%d %s%s'):format(quantity, self.desc, quantity == 1 and '' or 's') end @@ -605,7 +639,7 @@ function PlannerOverlay:init() local main_panel = widgets.Panel{ view_id='main', - frame={t=1, l=0, r=0, h=14}, + frame={t=1, l=0, r=0, h=15}, frame_style=gui.FRAME_INTERIOR_MEDIUM, frame_background=gui.CLEAR_PEN, visible=self:callback('is_not_minimized'), @@ -653,6 +687,43 @@ function PlannerOverlay:init() local buildingplan = require('plugins.buildingplan') + -- WeaponSpiketrapPanel defined outside of main_panel, otherwise addviews breaks -> addviews expects table + local WeaponSpiketrapPanel = defclass(WeaponSpiketrapPanel, widgets.Panel) + WeaponSpiketrapPanel.ATTRS{ + view_id='weapons', + visible=is_weapon_or_spike_trap, + } + + function WeaponSpiketrapPanel:init() + self.options = utils.tabulate(function(i) return {label='('..i..')', value=i, pen=COLOR_YELLOW} end, 1, 10) + + self:addviews{ + widgets.CycleHotkeyLabel{ + view_id='weapons_hotkey', + frame={b=5, l=1, w=28}, + key='CUSTOM_T', + key_back='CUSTOM_SHIFT_T', + label='Number of weapons:', + options=self.options, + initial_option=weapon_quantity, + on_change=function(val) + weapon_quantity = val + end + }, + + widgets.Slider{ + view_id='weapons_slider', + frame={b=7, l=4, w=35}, + num_stops=#self.options, + get_idx_fn=function() return weapon_quantity end, + on_change=function(val) + weapon_quantity = val + self.subviews.weapons_hotkey:setOption(val) + end + } + } + end + main_panel:addviews{ widgets.Label{ frame={}, @@ -678,7 +749,7 @@ function PlannerOverlay:init() on_clear_filter=self:callback('clear_filter')}, widgets.CycleHotkeyLabel{ view_id='hollow', - frame={b=4, l=1, w=21}, + frame={b=5, l=1, w=21}, key='CUSTOM_H', label='Hollow area:', visible=is_construction, @@ -689,7 +760,7 @@ function PlannerOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='stairs_top_subtype', - frame={b=7, l=1, w=30}, + frame={b=8, l=1, w=30}, key='CUSTOM_R', label='Top stair type: ', visible=is_multi_level_stairs, @@ -701,7 +772,7 @@ function PlannerOverlay:init() }, widgets.CycleHotkeyLabel { view_id='stairs_bottom_subtype', - frame={b=6, l=1, w=30}, + frame={b=7, l=1, w=30}, key='CUSTOM_B', label='Bottom Stair Type:', visible=is_multi_level_stairs, @@ -713,7 +784,7 @@ function PlannerOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='stairs_only_subtype', - frame={b=7, l=1, w=30}, + frame={b=8, l=1, w=30}, key='CUSTOM_R', label='Single level stair:', visible=is_single_level_stairs, @@ -723,19 +794,12 @@ function PlannerOverlay:init() {label='Down', value=df.construction_type.DownStair}, }, }, - widgets.CycleHotkeyLabel { -- TODO: this thing also needs a slider - view_id='weapons', - frame={b=4, l=1, w=28}, - key='CUSTOM_T', - key_back='CUSTOM_SHIFT_T', - label='Number of weapons:', - visible=is_weapon_or_spike_trap, - options=utils.tabulate(function(i) return {label='('..i..')', value=i, pen=COLOR_YELLOW} end, 1, 10), - on_change=function(val) weapon_quantity = val end, - }, + + WeaponSpiketrapPanel{}, + widgets.ToggleHotkeyLabel { view_id='engraved', - frame={b=4, l=1, w=22}, + frame={b=5, l=1, w=22}, key='CUSTOM_T', label='Engraved only:', visible=is_slab, @@ -745,7 +809,7 @@ function PlannerOverlay:init() }, widgets.ToggleHotkeyLabel { view_id='empty', - frame={b=4, l=1, w=22}, + frame={b=5, l=1, w=22}, key='CUSTOM_T', label='Empty only:', visible=is_cage, @@ -756,6 +820,16 @@ function PlannerOverlay:init() widgets.Panel{ visible=function() return #get_cur_filters() > 0 end, subviews={ + widgets.HotkeyLabel{ + frame={b=3, l=1, w=22}, + key='CUSTOM_CTRL_Q', + label='Queue order', + on_activate=function() self:queue_order(self.selected) end, + visible=function() + local item = self.subviews['item'..tostring(self.selected)] + return item and item.available and item.quantity and (item.available < item.quantity) + end + }, widgets.HotkeyLabel{ frame={b=2, l=1, w=22}, key='CUSTOM_F', @@ -836,7 +910,7 @@ function PlannerOverlay:init() local error_panel = widgets.ResizingPanel{ view_id='errors', - frame={t=15, l=0, r=0}, + frame={t=16, l=0, r=0}, frame_style=gui.BOLD_FRAME, frame_background=gui.CLEAR_PEN, visible=self:callback('is_not_minimized'), @@ -898,7 +972,7 @@ function PlannerOverlay:init() local favorites_panel = widgets.Panel{ view_id='favorites', - frame={t=15, l=0, r=0, h=9}, + frame={t=16, l=0, r=0, h=9}, frame_style=gui.FRAME_INTERIOR_MEDIUM, frame_background=gui.CLEAR_PEN, visible=self:callback('show_favorites'), @@ -935,7 +1009,7 @@ function PlannerOverlay:init() is_selected_fn=make_is_selected_filter('0') }, widgets.CycleHotkeyLabel { view_id='slot_select', - frame={b=0, l=2}, + frame={b=2, l=2}, key='CUSTOM_X', key_back='CUSTOM_SHIFT_X', label='next/previous slot', @@ -945,11 +1019,16 @@ function PlannerOverlay:init() on_change=function(val) self.selected_favorite = val end, }, widgets.HotkeyLabel{ - frame={b=0, l=28}, + frame={b=2, l=28}, label="set/apply selected", key='CUSTOM_Y', on_activate=function () self:save_restore_filter(self.selected_favorite) end, }, + widgets.TooltipLabel { + frame={b=0, l=2}, + show_tooltip=true, + text="Shift+click to edit the label of a favorite", + }, } } @@ -969,7 +1048,7 @@ function PlannerOverlay:show_favorites() end function PlannerOverlay:show_hide_favorites(new) - local errors_frame = {t=15+(new and 9 or 0), l=0, r=0} + local errors_frame = {t=16+(new and 9 or 0), l=0, r=0} self.subviews.errors.frame = errors_frame self:updateLayout() end @@ -1038,6 +1117,74 @@ function PlannerOverlay:clear_filter(idx) desc=require('plugins.buildingplan').clearFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, idx-1) end +function PlannerOverlay:queue_order(idx) + local item = self.subviews['item'..tostring(idx)] + if not item or not item.available or not item.quantity or item.available >= item.quantity then return end + local missing = item.quantity - item.available + if missing <= 0 then return end + + local filter = get_cur_filters()[idx] + + local job_name = "ConstructBlocks" + local item_type = nil + if filter.item_type and filter.item_type ~= -1 then + item_type = filter.item_type + elseif filter.vector_id and filter.vector_id ~= -1 then + local mapping_vector = { + [df.job_item_vector_id.ANY_WEAPON] = df.item_type.WEAPON, + [df.job_item_vector_id.ANY_ARMOR] = df.item_type.ARMOR, + } + item_type = mapping_vector[filter.vector_id] + end + + if item_type and ITEM_TO_JOB[item_type] then + job_name = ITEM_TO_JOB[item_type] + end + + local order_json = { + job = job_name, + amount_total = missing + } + + local buildingplan = require('plugins.buildingplan') + local cats_list = {} + if buildingplan.hasFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, idx - 1) then + local cats = buildingplan.getMaterialMaskFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, idx - 1) + for cat, enabled in pairs(cats) do + if enabled and cat ~= 'unset' then + table.insert(cats_list, cat) + end + end + end + + if #cats_list == 0 then + -- df manager requires a material category for generic jobs or it queues "unknown material" orders + table.insert(cats_list, JOB_DEFAULTS[job_name] or 'stone') + end + + local mat_cats = {} + for _, cat in ipairs(cats_list) do + if VALID_MAT_CATS[cat] then + table.insert(mat_cats, cat) + elseif cat == 'stone' then + order_json.material = "INORGANIC" + elseif cat == 'glass' then + order_json.material = "GLASS_GREEN" + elseif cat == 'metal' or cat == 'iron' then + order_json.material = "IRON" + end + end + + if #mat_cats > 0 then + order_json.material_category = mat_cats + end + + dfhack.run_command_silent('workorder', json.encode(order_json)) + + local desc = item.desc or "item" + dfhack.gui.showAnnouncement('Work order queued for ' .. tostring(missing) .. ' ' .. desc .. '.', COLOR_YELLOW, true) +end + local function get_placement_data() local direction = uibs.direction local bounds = get_selected_bounds() @@ -1307,10 +1454,16 @@ function PlannerOverlay:place_building(placement_data, chosen_items) if is_stairs() then subtype = self:get_stairs_subtype(pos, pd) end + local fields = {} + if is_siege_engine() then + local facing = df.global.buildreq.direction + fields.facing = facing + fields.resting_orientation = facing + end local bld, err = dfhack.buildings.constructBuilding{pos=pos, type=uibs.building_type, subtype=subtype, custom=uibs.custom_type, width=pd.width, height=pd.height, - direction=uibs.direction, filters=filters} + direction=uibs.direction, filters=filters, fields=fields} if err then -- it's ok if some buildings fail to build goto continue diff --git a/plugins/lua/buildingplan/unlink_mechanisms.lua b/plugins/lua/buildingplan/unlink_mechanisms.lua index 86ae80940f..81bd0fdabe 100644 --- a/plugins/lua/buildingplan/unlink_mechanisms.lua +++ b/plugins/lua/buildingplan/unlink_mechanisms.lua @@ -4,6 +4,7 @@ local dialogs = require('gui.dialogs') local overlay = require("plugins.overlay") local utils = require("utils") local widgets = require("gui.widgets") +local lever = reqscript("lever") local function mech_iter(b) --iterate mechanisms backwards local t = b.contained_items @@ -35,6 +36,40 @@ local function get_mech_target(m) --mechanism target building if exists return i and df.building.find(m.general_refs[i].building_id) or nil end +local function is_lever(b) --building is a lever + return b and b._type == df.building_trapst and b.trap_type == df.trap_type.Lever +end + +local function get_pull_job(b) --pending pull job on lever, or nil + for _, j in ipairs(b.jobs) do + if j.job_type == df.job_type.PullLever then + return j + end + end +end + +local ASCII_LEVER_OFF = string.char(0x95) --ò +local ASCII_LEVER_ON = string.char(0xA2) --ó + +local function get_lever_state_char(lever) --lever position glyph for the current tileset + -- match the current mode because ASCII and premium lever glyphs differ in directions + if dfhack.screen.inGraphicsMode() then + return lever.state == 0 and "/" or "\\" + end + return lever.state == 0 and ASCII_LEVER_OFF or ASCII_LEVER_ON +end + +local function get_pull_label(lever) --button label and pen for the lever's pull state + local state_char = get_lever_state_char(lever) --current lever position + local job = get_pull_job(lever) + if not job then + return "Pull "..state_char, COLOR_WHITE + elseif dfhack.job.getWorker(job) then + return "Pulling "..state_char, COLOR_GREEN --a citizen has taken the job + end + return "Queued "..state_char, COLOR_YELLOW --queued, not yet taken +end + local function has_link_tab(b) --linked building tab exists if not b then return @@ -148,7 +183,7 @@ local valid_build = { MechLinkOverlay = defclass(MechLinkOverlay, overlay.OverlayWidget) MechLinkOverlay.ATTRS { - desc = "Allows unlinking mechanisms from buildings.", + desc = "Allows unlinking mechanisms and pulling linked levers from buildings.", default_enabled = true, default_pos = {x=-41, y=-4}, frame = {w=56, h=27}, @@ -303,6 +338,55 @@ function MechLinkOverlay:activate_button(n) end end +function MechLinkOverlay:get_pull_button(n, ensure) + local button = self.subviews["pull_"..n] + if not button and ensure then + self:addviews + { + widgets.TextButton + { + view_id = "pull_"..n, + frame = {t=0, r=17, w=11, h=1}, + label = "", --set per-frame in update_buttons + on_activate = function() self:activate_pull(n) end, + visible = false, + }, + } + button = self.subviews["pull_"..n] + button:updateLayout(self.frame_body) + end + + return button +end + +function MechLinkOverlay:pull_target(n) --linked lever for button n, or nil + local button = self:get_pull_button(n) + if not button then + return + end + + local idx = self:idx_from_offset(button.frame.t) + if idx > 0 and idx < #self.building.contained_items then + local target = get_mech_target(self.building.contained_items[idx].item) + if is_lever(target) then + return target + end + end +end + +function MechLinkOverlay:activate_pull(n) + local target = self:pull_target(n) + if not target then + return + end + local job = get_pull_job(target) + if job then + dfhack.job.removeJob(job) --cancel queued pull + else + lever.leverPullJob(target, true) --do now + end +end + function MechLinkOverlay:ask_unlink_all() local saved_mode = self.subviews.unlink_mode:getOptionValue() local message = { @@ -356,6 +440,20 @@ function MechLinkOverlay:update_buttons() button.visible = true end button:updateLayout() + + local pbutton = self:get_pull_button(i, true) + pbutton.visible = false + local target = idx > 0 and idx < bci_len and + get_mech_target(self.building.contained_items[idx].item) + if is_lever(target) then + local label, pen = get_pull_label(target) + pbutton:setLabel(label) + pbutton.label.text_pen = pen + pbutton.frame.t = offset + pbutton.frame.r = h_offset + 9 + pbutton.visible = true + end + pbutton:updateLayout() end local b = (self.frame.h % 3) == 1 and #self.links >= self.num_buttons and 0 or 1 @@ -371,6 +469,10 @@ function MechLinkOverlay:preUpdateLayout(parent_rect) if button then button.visible = false end + local pbutton = self:get_pull_button(i) + if pbutton then + pbutton.visible = false + end end local h = parent_rect.height - 49 diff --git a/plugins/lua/dig.lua b/plugins/lua/dig.lua index babdb3b7ab..192989d8a1 100644 --- a/plugins/lua/dig.lua +++ b/plugins/lua/dig.lua @@ -4,7 +4,7 @@ local gui = require('gui') local overlay = require('plugins.overlay') local widgets = require('gui.widgets') -local toolbar_textures = dfhack.textures.loadTileset('hack/data/art/damp_dig_toolbar.png', 8, 12, true) +local toolbar_textures = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/damp_dig_toolbar.png', 8, 12, true) local main_if = df.global.game.main_interface local selection_rect = df.global.selection_rect diff --git a/plugins/lua/dwarfmonitor.lua b/plugins/lua/dwarfmonitor.lua index d98bfd80f5..fd18178a51 100644 --- a/plugins/lua/dwarfmonitor.lua +++ b/plugins/lua/dwarfmonitor.lua @@ -5,7 +5,7 @@ local guidm = require('gui.dwarfmode') local overlay = require('plugins.overlay') local utils = require('utils') -local DWARFMONITOR_CONFIG_FILE = 'dfhack-config/dwarfmonitor.json' +local DWARFMONITOR_CONFIG_FILE = dfhack.getConfigPath() .. '/dwarfmonitor.json' -- ------------- -- -- WeatherWidget -- diff --git a/plugins/lua/hotkeys.lua b/plugins/lua/hotkeys.lua index d0cecb5ba0..5c04fba2df 100644 --- a/plugins/lua/hotkeys.lua +++ b/plugins/lua/hotkeys.lua @@ -5,7 +5,7 @@ local helpdb = require('helpdb') local overlay = require('plugins.overlay') local widgets = require('gui.widgets') -local logo_textures = dfhack.textures.loadTileset('hack/data/art/logo.png', 8, 12, true) +local logo_textures = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/logo.png', 8, 12, true) local function get_command(cmdline) local first_word = cmdline:trim():split(' +')[1] diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 2774bd80ee..a470e3e2ce 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -38,7 +38,7 @@ local function do_import() dismiss_on_select2=false, on_select2=function(_, choice) if choice.text:startswith('library/') then return end - local fname = 'dfhack-config/orders/'..choice.text..'.json' + local fname = dfhack.getConfigPath() .. '/orders/' .. choice.text .. '.json' if not dfhack.filesystem.isfile(fname) then return end dialogs.showYesNoPrompt('Delete orders file?', 'Are you sure you want to delete "' .. fname .. '"?', nil, @@ -74,10 +74,11 @@ local mi = df.global.game.main_interface OrdersOverlay = defclass(OrdersOverlay, overlay.OverlayWidget) OrdersOverlay.ATTRS{ desc='Adds import, export, and other functions to the manager orders screen.', - default_pos={x=53,y=-6}, + default_pos={x=41,y=-6}, default_enabled=true, viewscreens='dwarfmode/Info/WORK_ORDERS/Default', frame={w=43, h=4}, + version=1, } function OrdersOverlay:init() @@ -709,11 +710,344 @@ function QuantityRightClickOverlay:onInput(keys) end end +-- +-- OrdersSearchOverlay +-- + +local ORDER_HEIGHT = 3 +local TABS_WIDTH_THRESHOLD = 155 +local LIST_START_Y_ONE_TABS_ROW = 8 +local LIST_START_Y_TWO_TABS_ROWS = 10 +local BOTTOM_MARGIN = 9 +local ARROW_X = 10 + +local SELECTED_PEN = dfhack.pen.parse{fg=COLOR_BLACK, bg=COLOR_WHITE, bold=true} +local MATCH_PEN = dfhack.pen.parse{fg=COLOR_WHITE, bg=COLOR_BLACK, bold=true} + +local function perform_search(text) + local matches = {} + + if text == '' then + return matches + end + + local orders = df.global.world.manager_orders.all + for i = 0, #orders - 1 do + local order = orders[i] + local search_key = dfhack.job.getManagerOrderName(order) + if search_key and utils.search_text(search_key, text) then + table.insert(matches, i) + end + end + + return matches +end + +local function concat_order_names() + local orders = df.global.world.manager_orders.all + if #orders == 0 then return "" end + + local names = {} + for i = 0, #orders - 1 do + local name = dfhack.job.getManagerOrderName(orders[i]) + table.insert(names, name or "") + end + + return table.concat(names, "|") +end + +local function getListStartY() + local rect = gui.get_interface_rect() + + if rect.width >= TABS_WIDTH_THRESHOLD then + return LIST_START_Y_ONE_TABS_ROW + else + return LIST_START_Y_TWO_TABS_ROWS + end +end + +local function getViewportSize() + local rect = gui.get_interface_rect() + local list_start_y = getListStartY() + + local available_height = rect.height - list_start_y - BOTTOM_MARGIN + return math.floor(available_height / ORDER_HEIGHT) +end + +local function getVisibleOrderIndices() + local orders = df.global.world.manager_orders.all + local scroll_pos = mi.info.work_orders.scroll_position_work_orders + + if #orders == 0 then return 0, -1 end + + local viewport_size = getViewportSize() + local viewport_start = scroll_pos + local viewport_end = scroll_pos + viewport_size - 1 + + -- Handle end-of-list case + if viewport_end >= #orders then + viewport_end = #orders - 1 + viewport_start = math.max(0, viewport_end - viewport_size + 1) + end + + return viewport_start, viewport_end +end + +local function calculateOrderY(order_idx) + local orders = df.global.world.manager_orders.all + + if #orders == 0 or order_idx < 0 or order_idx >= #orders then + return nil + end + + local viewport_start, viewport_end = getVisibleOrderIndices() + + -- Check if order is in viewport + if order_idx < viewport_start or order_idx > viewport_end then + return nil + end + + local list_start_y = getListStartY() + local pos_in_viewport = order_idx - viewport_start + + return list_start_y + (pos_in_viewport * ORDER_HEIGHT) +end + +OrdersSearchOverlay = defclass(OrdersSearchOverlay, overlay.OverlayWidget) +OrdersSearchOverlay.ATTRS{ + desc='Adds a search box to find and navigate to matching manager orders.', + default_pos={x=85, y=-6}, + default_enabled=true, + viewscreens='dwarfmode/Info/WORK_ORDERS/Default', + frame={w=26, h=4}, + overlay_onupdate_max_freq_seconds=1, +} + +function OrdersSearchOverlay:init() + local main_panel = widgets.Panel{ + view_id='main_panel', + frame={t=0, l=0, r=0, h=4}, + frame_style=gui.MEDIUM_FRAME, + frame_background=gui.CLEAR_PEN, + frame_title='Search', + visible=function() return not self.minimized end, + subviews={ + widgets.EditField{ + view_id='filter', + frame={t=0, l=0}, + key='CUSTOM_ALT_S', + on_change=self:callback('update_filter'), + on_submit=self:callback('on_submit'), + on_submit2=self:callback('on_submit2'), + }, + widgets.HotkeyLabel{ + frame={t=1, l=0}, + label='prev', + key='CUSTOM_ALT_P', + auto_width=true, + on_activate=self:callback('cycle_match', -1), + enabled=function() return #self.matched_indices > 0 end, + }, + widgets.HotkeyLabel{ + frame={t=1, l=12}, + label='next', + key='CUSTOM_ALT_N', + auto_width=true, + on_activate=self:callback('cycle_match', 1), + enabled=function() return #self.matched_indices > 0 end, + }, + }, + } + + local minimized_panel = widgets.Panel{ + frame={t=0, r=0, w=3, h=1}, + subviews={ + widgets.Label{ + frame={t=0, l=0, w=1, h=1}, + text='[', + text_pen=COLOR_RED, + visible=function() return self.minimized end, + }, + widgets.Label{ + frame={t=0, l=1, w=1, h=1}, + text={{text=function() return self.minimized and string.char(31) or string.char(30) end}}, + text_pen=dfhack.pen.parse{fg=COLOR_BLACK, bg=COLOR_GREY}, + text_hpen=dfhack.pen.parse{fg=COLOR_BLACK, bg=COLOR_WHITE}, + on_click=function() self.minimized = not self.minimized end, + }, + widgets.Label{ + frame={t=0, r=0, w=1, h=1}, + text=']', + text_pen=COLOR_RED, + visible=function() return self.minimized end, + }, + }, + } + + self:addviews{ + main_panel, + minimized_panel, + } + + self.minimized = false + self.matched_indices = {} + self.current_match_idx = 0 + self.cached_order_names = nil +end + +function OrdersSearchOverlay:overlay_onupdate() + if self.minimized then return end + + local current_order_names = concat_order_names() + if current_order_names ~= self.cached_order_names then + self.cached_order_names = current_order_names + self:update_filter() + end +end + +function OrdersSearchOverlay:update_filter() + local text = self.subviews.filter.text + self.matched_indices = perform_search(text) + self.current_match_idx = 0 + + if text == '' then + self.subviews.main_panel.frame_title = 'Search' + else + self.subviews.main_panel.frame_title = 'Search' .. self:get_match_text() + end +end + +function OrdersSearchOverlay:on_submit() + self:cycle_match(1) + self.subviews.filter:setFocus(true) +end + +function OrdersSearchOverlay:on_submit2() + self:cycle_match(-1) + self.subviews.filter:setFocus(true) +end + +function OrdersSearchOverlay:cycle_match(direction) + local search_text = self.subviews.filter.text + + local new_matches = perform_search(search_text) + + if #new_matches == 0 then + self.matched_indices = {} + self.current_match_idx = 0 + self.subviews.main_panel.frame_title = 'Search' + return + end + + local new_match_idx = self.current_match_idx + direction + + if new_match_idx > #new_matches then + new_match_idx = 1 + elseif new_match_idx < 1 then + new_match_idx = #new_matches + end + + self.matched_indices = new_matches + self.current_match_idx = new_match_idx + + -- Scroll to the selected match only if not already visible + local order_idx = self.matched_indices[self.current_match_idx] + local viewport_start, viewport_end = getVisibleOrderIndices() + if order_idx < viewport_start or order_idx > viewport_end then + mi.info.work_orders.scroll_position_work_orders = order_idx + end + + self.subviews.main_panel.frame_title = 'Search' .. self:get_match_text() +end + +function OrdersSearchOverlay:get_match_text() + local total_matches = #self.matched_indices + + if total_matches == 0 then + return '' + end + + if self.current_match_idx == 0 then + return string.format(': %d matches', total_matches) + end + + return string.format(': %d of %d', self.current_match_idx, total_matches) +end + +local function is_mouse_key(keys) + return keys._MOUSE_L + or keys._MOUSE_R + or keys._MOUSE_M + or keys.CONTEXT_SCROLL_UP + or keys.CONTEXT_SCROLL_DOWN + or keys.CONTEXT_SCROLL_PAGEUP + or keys.CONTEXT_SCROLL_PAGEDOWN +end + +function OrdersSearchOverlay:onInput(keys) + if mi.job_details.open then return end + + local filter_field = self.subviews.filter + if not filter_field then return false end + + -- Unfocus search on right-click + if keys._MOUSE_R and filter_field.focus then + filter_field:setFocus(false) + return true + end + + -- Let parent handle input first (for HotkeyLabel clicks and widget interactions) + if OrdersSearchOverlay.super.onInput(self, keys) then + return true + end + + -- Unfocus search on left-click when focused (for workshop and number of times changes) + -- And let the click pass through + if keys._MOUSE_L and filter_field.focus then + filter_field:setFocus(false) + return false + end + + -- Only consume input if search field has focus and it's not a mouse key + -- This allows scrolling, navigation, and mouse interaction in the orders list + if filter_field.focus and not is_mouse_key(keys) then + return true + end + + return false +end + +function OrdersSearchOverlay:render(dc) + if mi.job_details.open then return end + OrdersSearchOverlay.super.render(self, dc) + self:render_highlights(dc) +end + +function OrdersSearchOverlay:render_highlights(dc) + if #self.matched_indices == 0 then return end + + local selected_order_idx = self.current_match_idx > 0 and + self.matched_indices[self.current_match_idx] or nil + + for _, match_order_idx in ipairs(self.matched_indices) do + local match_y = calculateOrderY(match_order_idx) + + if match_y then + local pen = (match_order_idx == selected_order_idx) and SELECTED_PEN or MATCH_PEN + + dc:seek(ARROW_X, match_y):string('|', pen) + dc:seek(ARROW_X, match_y + 1):string('>', pen) + dc:seek(ARROW_X, match_y + 2):string('|', pen) + end + end +end + -- ------------------- OVERLAY_WIDGETS = { recheck=RecheckOverlay, importexport=OrdersOverlay, + search=OrdersSearchOverlay, skillrestrictions=SkillRestrictionOverlay, laborrestrictions=LaborRestrictionsOverlay, conditionsrightclick=ConditionsRightClickOverlay, diff --git a/plugins/lua/overlay.lua b/plugins/lua/overlay.lua index c1d1bcb434..cdfaac89e1 100644 --- a/plugins/lua/overlay.lua +++ b/plugins/lua/overlay.lua @@ -6,7 +6,7 @@ local scriptmanager = require('script-manager') local utils = require('utils') local widgets = require('gui.widgets') -local OVERLAY_CONFIG_FILE = 'dfhack-config/overlay.json' +local OVERLAY_CONFIG_FILE = dfhack.getConfigPath() .. '/overlay.json' local OVERLAY_WIDGETS_VAR = 'OVERLAY_WIDGETS' local GLOBAL_KEY = 'OVERLAY' diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua index bdad4ae90e..af066fb175 100644 --- a/plugins/lua/sort.lua +++ b/plugins/lua/sort.lua @@ -293,7 +293,7 @@ local function get_mental_stability(unit) local emotionally_obsessive = unit.status.current_soul.personality.traits.EMOTIONALLY_OBSESSIVE local humor = unit.status.current_soul.personality.traits.HUMOR local love_propensity = unit.status.current_soul.personality.traits.LOVE_PROPENSITY - local perseverence = unit.status.current_soul.personality.traits.PERSEVERENCE + local perseverance = unit.status.current_soul.personality.traits.PERSEVERANCE local politeness = unit.status.current_soul.personality.traits.POLITENESS local privacy = unit.status.current_soul.personality.traits.PRIVACY local stress_vulnerability = unit.status.current_soul.personality.traits.STRESS_VULNERABILITY @@ -315,7 +315,7 @@ local function get_mental_stability(unit) + (anxiety_propensity * -0.06) + (bravery * 0.06) + (cheer_propensity * 0.41) + (curious * -0.06) + (discord * 0.14) + (dutifulness * -0.03) + (emotionally_obsessive * -0.13) - + (humor * -0.05) + (love_propensity * 0.15) + (perseverence * -0.07) + + (humor * -0.05) + (love_propensity * 0.15) + (perseverance * -0.07) + (politeness * -0.14) + (privacy * 0.03) + (stress_vulnerability * -0.20) + (tolerant * -0.11) @@ -1029,12 +1029,29 @@ function SquadFilterOverlay:init() local left_panel = widgets.Panel{ view_id='left_panel', - frame={t=1, b=0, l=0, w=NARROW_WIDTH-4}, + frame={t=0, b=0, l=0, w=NARROW_WIDTH-4}, visible=true, subviews={ + widgets.HotkeyLabel{ + view_id='toggle_all', + frame={t=0, l=0}, + key='CUSTOM_SHIFT_A', + label='Toggle all', + on_activate=function() + local target = self.subviews.military:getOptionValue() == 'exclude' and 'include' or 'exclude' + self.subviews.military:setOption(target) + self.subviews.officials:setOption(target) + self.subviews.nobles:setOption(target) + self.subviews.infant:setOption(target) + self.subviews.unstable:setOption(target) + self.subviews.maimed:setOption(target) + self.subviews.labor_conflict:setOption(target) + poke_list() + end, + }, widgets.CycleHotkeyLabel{ view_id='military', - frame={t=0, l=0}, + frame={t=1, l=0}, key='CUSTOM_SHIFT_Q', label='Other squads:', options={ @@ -1047,7 +1064,7 @@ function SquadFilterOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='officials', - frame={t=1, l=0}, + frame={t=2, l=0}, key='CUSTOM_SHIFT_O', label=' Officials:', options={ @@ -1060,7 +1077,7 @@ function SquadFilterOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='nobles', - frame={t=2, l=0}, + frame={t=3, l=0}, key='CUSTOM_SHIFT_N', label=' Nobility:', options={ @@ -1076,12 +1093,25 @@ function SquadFilterOverlay:init() local right_panel = widgets.Panel{ view_id='right_panel', - frame={t=1, b=0, r=2, w=NARROW_WIDTH-4}, + frame={t=0, b=0, r=2, w=NARROW_WIDTH-4}, visible=false, subviews={ widgets.CycleHotkeyLabel{ - view_id='infant', + view_id='labor_conflict', frame={t=0, l=0}, + key='CUSTOM_SHIFT_U', + label=' Uniformed:', + options={ + {label='Include', value='include', pen=COLOR_GREEN}, + {label='Only', value='only', pen=COLOR_YELLOW}, + {label='Exclude', value='exclude', pen=COLOR_LIGHTRED}, + }, + initial_option='include', + on_change=poke_list, + }, + widgets.CycleHotkeyLabel{ + view_id='infant', + frame={t=1, l=0}, key='CUSTOM_SHIFT_M', label='With infants:', options={ @@ -1094,7 +1124,7 @@ function SquadFilterOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='unstable', - frame={t=1, l=0}, + frame={t=2, l=0}, key='CUSTOM_SHIFT_D', label='Hates combat:', options={ @@ -1107,7 +1137,7 @@ function SquadFilterOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='maimed', - frame={t=2, l=0}, + frame={t=3, l=0}, key='CUSTOM_SHIFT_I', label=' Maimed:', options={ @@ -1125,21 +1155,6 @@ function SquadFilterOverlay:init() frame_style=gui.FRAME_MEDIUM, frame_background=gui.CLEAR_PEN, subviews={ - widgets.HotkeyLabel{ - frame={t=0, w=NARROW_WIDTH-3}, - key='CUSTOM_SHIFT_A', - label='Toggle all filters', - on_activate=function() - local target = self.subviews.military:getOptionValue() == 'exclude' and 'include' or 'exclude' - self.subviews.military:setOption(target) - self.subviews.officials:setOption(target) - self.subviews.nobles:setOption(target) - self.subviews.infant:setOption(target) - self.subviews.unstable:setOption(target) - self.subviews.maimed:setOption(target) - poke_list() - end, - }, left_panel, widgets.Label{ view_id='shifter', @@ -1167,9 +1182,8 @@ function SquadFilterOverlay:init() main_panel, widgets.Divider{ view_id='divider', - frame={l=NARROW_WIDTH-1, w=1, t=2}, + frame={l=NARROW_WIDTH-1, w=1, t=0}, frame_style=gui.FRAME_MEDIUM, - frame_style_t=false, visible=false, }, widgets.HelpButton{ @@ -1253,6 +1267,12 @@ local function is_maimed(unit) unit.status2.limbs_stand_count == 0 end +local function has_labor_conflict(unit) + return unit.status.labors[df.unit_labor.MINE] or + unit.status.labors[df.unit_labor.CUTWOOD] or + unit.status.labors[df.unit_labor.HUNT] +end + local function filter_matches(unit, filter) if filter.military == 'only' and not is_in_military(unit) then return false end if filter.military == 'exclude' and is_in_military(unit) then return false end @@ -1266,6 +1286,8 @@ local function filter_matches(unit, filter) if filter.unstable == 'exclude' and is_unstable(unit) then return false end if filter.maimed == 'only' and not is_maimed(unit) then return false end if filter.maimed == 'exclude' and is_maimed(unit) then return false end + if filter.labor_conflict == 'only' and not has_labor_conflict(unit) then return false end + if filter.labor_conflict == 'exclude' and has_labor_conflict(unit) then return false end return true end @@ -1281,6 +1303,7 @@ function do_squad_filter(unit) infant=self.subviews.infant:getOptionValue(), unstable=self.subviews.unstable:getOptionValue(), maimed=self.subviews.maimed:getOptionValue(), + labor_conflict=self.subviews.labor_conflict:getOptionValue(), } return filter_matches(unit, filter) end @@ -1294,6 +1317,7 @@ OVERLAY_WIDGETS = { candidates=require('plugins.sort.info').CandidatesOverlay, interrogation=require('plugins.sort.info').InterrogationOverlay, conviction=require('plugins.sort.info').ConvictionOverlay, + deathcause_button=require('plugins.sort.deathcause_button').DeathCauseOverlay, location_selector=require('plugins.sort.locationselector').LocationSelectorOverlay, -- TODO: maybe rewrite for 50.12 -- burrow_assignment=require('plugins.sort.unitselector').BurrowAssignmentOverlay, diff --git a/plugins/lua/sort/deathcause_button.lua b/plugins/lua/sort/deathcause_button.lua new file mode 100644 index 0000000000..b149601a7a --- /dev/null +++ b/plugins/lua/sort/deathcause_button.lua @@ -0,0 +1,78 @@ +local _ENV = mkmodule('plugins.sort.deathcause_button') + +local dialogs = require('gui.dialogs') +local overlay = require('plugins.overlay') +local widgets = require('gui.widgets') + +DeathCauseOverlay = defclass(DeathCauseOverlay, overlay.OverlayWidget) +DeathCauseOverlay.ATTRS{ + desc='Adds a button to view death cause on the dead/missing tab.', + default_pos={x=50, y=-7}, + default_enabled=true, + viewscreens='dwarfmode/Info/CREATURES/DECEASED', + frame={w=21, h=1}, +} + +function DeathCauseOverlay:init() + local deathcause = reqscript('deathcause') + + local function get_selected_unit() + -- Navigate to the creatures/deceased widget hierarchy: + -- list_widget - the main deceased list + -- ├─ children[0]: scrollbar widget + -- └─ children[1]: container widget (list_container) + -- ├─ grandchildren[0]: header + -- ├─ grandchildren[1]: header or other UI + -- └─ grandchildren[2]: scrollable rows container (scrollable_list) + -- └─ rows: row widgets (each row = one unit in the list) + -- └─ row.children[x]: unit widget + -- └─ unit_widget.u: pointer to the df.unit object + + local creatures = df.global.game.main_interface.info.creatures + local list_widget = dfhack.gui.getWidget(creatures, 'Tabs', 'Dead/Missing') + if not list_widget then return nil end + + local children = dfhack.gui.getWidgetChildren(list_widget) + local list_container = children[1] + local grandchildren = dfhack.gui.getWidgetChildren(list_container) + + local scrollable_list = grandchildren[2] + if not scrollable_list then + return nil + end + + local rows = dfhack.gui.getWidgetChildren(scrollable_list) + + local cursor_idx = list_widget.cursor_idx or 0 + + if cursor_idx >= 0 and cursor_idx < #rows then + local row = rows[cursor_idx + 1] + + local ok, unit = pcall(function() return dfhack.gui.getWidget(row, 0).u end) + if ok and unit then + return unit + end + end + + return nil + end + + self:addviews{ + widgets.TextButton{ + frame={t=0, l=0}, + label='Show death cause', + key='CUSTOM_D', + on_activate=function() + local unit = get_selected_unit() + if not unit then + dialogs.showMessage('Death Cause', 'No unit selected.') + return + end + local cause = deathcause.getDeathCause(unit) + dialogs.showMessage('Death Cause', dfhack.df2console(cause)) + end, + }, + } +end + +return _ENV diff --git a/plugins/lua/sort/info.lua b/plugins/lua/sort/info.lua index feadee99c0..4f0462dfb7 100644 --- a/plugins/lua/sort/info.lua +++ b/plugins/lua/sort/info.lua @@ -278,8 +278,8 @@ function InfoOverlay:get_key() end function resize_overlay(self) - local sw = dfhack.screen.getWindowSize() - local overlay_width = math.min(40, sw-(self.frame_rect.x1 + 30)) + local iw = gui.get_interface_rect().width + local overlay_width = math.min(40, iw - (self.frame_rect.x1 + 30)) if overlay_width ~= self.frame.w then self.frame.w = overlay_width return true @@ -292,15 +292,13 @@ end function get_panel_offsets() local tabs_in_two_rows = is_tabs_in_two_rows() - local shift_right = info.current_mode == df.info_interface_mode_type.ARTIFACTS or - info.current_mode == df.info_interface_mode_type.LABOR + local shift_right = info.current_mode == df.info_interface_mode_type.ARTIFACTS local l_offset = (not tabs_in_two_rows and shift_right) and 4 or 0 local t_offset = 1 if tabs_in_two_rows then t_offset = shift_right and 0 or 3 end - if info.current_mode == df.info_interface_mode_type.JOBS or - info.current_mode == df.info_interface_mode_type.BUILDINGS then + if info.current_mode == df.info_interface_mode_type.JOBS then t_offset = t_offset - 1 end return l_offset, t_offset diff --git a/plugins/lua/sort/places.lua b/plugins/lua/sort/places.lua index f2588cc0ff..c353d5a948 100644 --- a/plugins/lua/sort/places.lua +++ b/plugins/lua/sort/places.lua @@ -170,6 +170,117 @@ local function get_farmplot_search_key(farmplot) return table.concat(result, ' ') end +---@param siege_engine df.building_siegeenginest +---@return string +local function siege_engine_type(siege_engine) + if siege_engine.type == df.siegeengine_type.BoltThrower then + return 'Bolt Thrower' + end + return df.siegeengine_type[siege_engine.type] +end + +---@param siege_engine df.building_siegeenginest +---@return string +local function siege_engine_status(siege_engine) + -- portions of return value with with underscores are to allow easier + -- word-anchored matching even when the DFHack full-text search mode is + -- enabled; e.g. + -- - "loaded" would match "Loaded" and "Unloaded", + -- - but "_loaded" would only match "_Loaded" + local count = 0 + local count_all = siege_engine.type == df.siegeengine_type.BoltThrower + for _, building_item in ipairs(siege_engine.contained_items) do + if building_item.use_mode == df.building_item_role_type.TEMP then + if not count_all then + return 'Loaded _Loaded' + end + count = count + building_item.item:getStackSize() + end + end + if count_all and count > 0 then + return ('%d bolts _%d_bolts'):format(count, count) + end + return 'Unloaded' +end + +---@param siege_engine df.building_siegeenginest +---@return string +local function siege_engine_job_status(siege_engine) + for _, job in ipairs(siege_engine.jobs) do + if job.job_type == df.job_type.LoadCatapult + or job.job_type == df.job_type.LoadBallista + or job.job_type == df.job_type.LoadBoltThrower + then + if dfhack.job.getWorker(job) ~= nil then + return 'Loading' + else + return 'Inactive load task' + end + end + local firing_bolt_thrower = job.job_type == df.job_type.FireBoltThrower + local firing = job.job_type == df.job_type.FireCatapult + or job.job_type == df.job_type.FireBallista + or firing_bolt_thrower + if firing then + local unit = dfhack.job.getWorker(job) + if unit == nil then + return 'No operator' + else + ---@type integer?, integer?, integer? + local x, y, z = dfhack.units.getPosition(unit) + -- DF shows "present" when the unit is inside the building's + -- footprint (or, for bolt throwers, next to it); the unit does + -- not need to be at the exact firing position tile (which + -- varies based on siege engine type and direction) + if x ~= nil and z == siege_engine.z then + ---@cast y integer + if firing_bolt_thrower then + if siege_engine.x1 - 1 <= x and x <= siege_engine.x2 + 1 + and siege_engine.y1 - 1 <= y and y <= siege_engine.y2 + 1 + then + return 'Operator present' + end + elseif dfhack.buildings.containsTile(siege_engine, x, y) then + return 'Operator present' + end + end + return 'Operator assigned' + end + end + end + return '' +end + +---@param siege_engine df.building_siegeenginest +---@return string +local function get_siege_engine_search_key(siege_engine) + -- DF 53.05 Info window, Places tab, Siege Engines subtab shows this info: + -- name: assigned name or siege engine type name + -- status: "Unloaded", "Loaded", " bolts" + -- job status: + -- - "Inactive load task" (load job unassigned), + -- - "Loading" (load job assigned), + -- - "No operator" (fire job unassigned), + -- - "Operator present" (fire job assigned), + -- - "Operator assigned" (fire job assigned, but not in position), + -- - blank + -- action: (icons) fire-at-will, practice, prepare-to-fire, keep-loaded, not-in-use + -- These have associated text blurbs that are shown in the + -- building info window, but those texts are not discoverable + -- from the Info > Places > Siege engine list view. + local result = {} + + if #siege_engine.name ~= 0 then table.insert(result, siege_engine.name) end + + table.insert(result, siege_engine_type(siege_engine)) + + table.insert(result, siege_engine_status(siege_engine)) + + table.insert(result, siege_engine_job_status(siege_engine)) + + return table.concat(result, ' ') +end + -- ---------------------- -- PlacesOverlay -- @@ -177,7 +288,8 @@ end PlacesOverlay = defclass(PlacesOverlay, sortoverlay.SortOverlay) PlacesOverlay.ATTRS{ desc='Adds search functionality to the places overview screens.', - default_pos={x=71, y=9}, + default_pos={x=52, y=9}, + version=2, viewscreens='dwarfmode/Info', frame={w=40, h=6} } @@ -205,6 +317,7 @@ function PlacesOverlay:init() self:register_handler('STOCKPILES', buildings.list[df.buildings_mode_type.STOCKPILES], curry(sortoverlay.single_vector_search, {get_search_key_fn=get_stockpile_search_key})) self:register_handler('WORKSHOPS', buildings.list[df.buildings_mode_type.WORKSHOPS], curry(sortoverlay.single_vector_search, {get_search_key_fn=get_workshop_search_key})) self:register_handler('FARMPLOTS', buildings.list[df.buildings_mode_type.FARMPLOTS], curry(sortoverlay.single_vector_search, {get_search_key_fn=get_farmplot_search_key})) + self:register_handler('SIEGE_ENGINES', buildings.list[df.buildings_mode_type.SIEGE_ENGINES], curry(sortoverlay.single_vector_search, {get_search_key_fn=get_siege_engine_search_key})) end function PlacesOverlay:get_key() diff --git a/plugins/lua/spectate.lua b/plugins/lua/spectate.lua index 953895eab0..1e0d81f917 100644 --- a/plugins/lua/spectate.lua +++ b/plugins/lua/spectate.lua @@ -75,7 +75,7 @@ end local function load_state() local state = get_default_state() - local config_file = json.open('dfhack-config/spectate.json') + local config_file = json.open(dfhack.getConfigPath() .. '/spectate.json') for key in pairs(config_file.data) do if state[key] == nil then config_file.data[key] = nil diff --git a/plugins/lua/stockpiles.lua b/plugins/lua/stockpiles.lua index ac48ed6fc9..4b56418861 100644 --- a/plugins/lua/stockpiles.lua +++ b/plugins/lua/stockpiles.lua @@ -7,8 +7,8 @@ local logistics = require('plugins.logistics') local overlay = require('plugins.overlay') local widgets = require('gui.widgets') -local STOCKPILES_DIR = 'dfhack-config/stockpiles' -local STOCKPILES_LIBRARY_DIR = 'hack/data/stockpiles' +local STOCKPILES_DIR = dfhack.getConfigPath() .. '/stockpiles' +local STOCKPILES_LIBRARY_DIR = dfhack.getHackPath() .. '/data/stockpiles' local BAD_FILENAME_REGEX = '[^%w._]' diff --git a/plugins/lua/suspendmanager.lua b/plugins/lua/suspendmanager.lua index c15be19402..86f0504ed3 100644 --- a/plugins/lua/suspendmanager.lua +++ b/plugins/lua/suspendmanager.lua @@ -26,6 +26,25 @@ function isBuildingPlanJob(job) return suspendmanager_isBuildingPlanJob(job) end +--- Return the selected construction job +local function getSelectedBuildingJob() + -- This is not relying on dfhack.gui.getSelectedJob() because we don't want + -- the job of a selected or followed unit, only of a selected building + local building = dfhack.gui.getSelectedBuilding(true) + if not building then + return nil + end + + -- Find if the building is being constructed + for _, job in ipairs(building.jobs) do + if job.job_type == df.job_type.ConstructBuilding then + return job + end + end + + return nil +end + function runOnce(prevent_blocking, quiet, unsuspend_everything) suspendmanager_runOnce(prevent_blocking, unsuspend_everything) if (not quiet) then @@ -69,7 +88,7 @@ function StatusOverlay:init() end function StatusOverlay:get_status_string() - local job = dfhack.gui.getSelectedJob(true) + local job = getSelectedBuildingJob() if job and job.flags.suspend then return "Suspended because: " .. suspendmanager_suspensionDescription(job) .. "." end @@ -77,8 +96,11 @@ function StatusOverlay:get_status_string() end function StatusOverlay:render(dc) - local job = dfhack.gui.getSelectedJob(true) - if not job or job.job_type ~= df.job_type.ConstructBuilding or not isEnabled() or isBuildingPlanJob(job) then + if not isEnabled() then + return + end + local job = getSelectedBuildingJob() + if not job or isBuildingPlanJob(job) then return end StatusOverlay.super.render(self, dc) @@ -110,8 +132,8 @@ function ToggleOverlay:init() end function ToggleOverlay:shouldRender() - local job = dfhack.gui.getSelectedJob(true) - return job and job.job_type == df.job_type.ConstructBuilding and not isBuildingPlanJob(job) + local job = getSelectedBuildingJob() + return job and not isBuildingPlanJob(job) end function ToggleOverlay:render(dc) @@ -133,7 +155,7 @@ end -- suspend overlay (formerly in unsuspend.lua) -local textures = dfhack.textures.loadTileset('hack/data/art/unsuspend.png', 32, 32, true) +local textures = dfhack.textures.loadTileset(dfhack.getHackPath()..'/data/art/unsuspend.png', 32, 32, true) local ok, buildingplan = pcall(require, 'plugins.buildingplan') if not ok then diff --git a/plugins/lua/zone.lua b/plugins/lua/zone.lua index 92940ef48e..2923f8e333 100644 --- a/plugins/lua/zone.lua +++ b/plugins/lua/zone.lua @@ -989,7 +989,9 @@ local function get_zone_status(unit_or_vermin, bld_assignments) return PASTURE_STATUS.ASSIGNED_HERE.value else local civzone = df.building.find(assigned_zone_ref.building_id) - if civzone.type == df.civzone_type.Pen then + if not civzone or not df.building_civzonest:is_instance(civzone) then + return PASTURE_STATUS.NONE.value + elseif civzone.type == df.civzone_type.Pen then return PASTURE_STATUS.PASTURED.value elseif civzone.type == df.civzone_type.Pond then return PASTURE_STATUS.PITTED.value @@ -1150,7 +1152,9 @@ local function get_cage_status(unit_or_vermin, bld_assignments) local assigned_zone_ref = get_general_ref(unit_or_vermin, df.general_ref_type.BUILDING_CIVZONE_ASSIGNED) if assigned_zone_ref then local civzone = df.building.find(assigned_zone_ref.building_id) - if civzone.type == df.civzone_type.Pen then + if not civzone or not df.building_civzonest:is_instance(civzone) then + return CAGE_STATUS.NONE.value + elseif civzone.type == df.civzone_type.Pen then return CAGE_STATUS.PASTURED.value elseif civzone.type == df.civzone_type.Pond then return CAGE_STATUS.PITTED.value diff --git a/plugins/misery.cpp b/plugins/misery.cpp index b81a4f1daa..7ecdb0d344 100644 --- a/plugins/misery.cpp +++ b/plugins/misery.cpp @@ -48,7 +48,7 @@ static command_result do_command(color_ostream &out, vector ¶meters) static void do_cycle(color_ostream &out); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(control,out).print("initializing %s\n", plugin_name); + DEBUG(control,out).print("initializing {}\n", plugin_name); // provide a configuration interface for the plugin commands.push_back(PluginCommand( @@ -61,19 +61,19 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -179,7 +179,7 @@ static void do_cycle(color_ostream &out) { // mark that we have recently run cycle_timestamp = world->frame_counter; - DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + DEBUG(cycle,out).print("running {} cycle\n", plugin_name); int strength = STRENGTH_MULTIPLIER * config.get_int(CONFIG_FACTOR); diff --git a/plugins/nestboxes.cpp b/plugins/nestboxes.cpp index bce36f9103..e471ac20d3 100644 --- a/plugins/nestboxes.cpp +++ b/plugins/nestboxes.cpp @@ -46,7 +46,7 @@ static void do_cycle(color_ostream &out); static command_result do_command(color_ostream &out, std::vector ¶meters); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(control,out).print("initializing %s\n", plugin_name); + DEBUG(control,out).print("initializing {}\n", plugin_name); // provide a configuration interface for the plugin commands.push_back(PluginCommand( @@ -59,19 +59,19 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector name.c_str()); + out.print("only protecting eggs inside burrow: {}\n", burrow->name); } else { @@ -159,7 +159,7 @@ static void printStatus(color_ostream &out){ static command_result do_command(color_ostream &out, std::vector ¶meters){ if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -182,7 +182,7 @@ static command_result do_command(color_ostream &out, std::vector ¶me // static void do_cycle(color_ostream &out) { - DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + DEBUG(cycle,out).print("running {} cycle\n", plugin_name); // mark that we have recently run cycle_timestamp = world->frame_counter; @@ -209,7 +209,7 @@ static void do_cycle(color_ostream &out) { if (sref && sref->data.job) Job::removeJob(sref->data.job); } - out.print("nestboxes: %d eggs %s\n", item->getStackSize(), fertile ? "forbidden" : "unforbidden"); + out.print("nestboxes: {} eggs {}\n", item->getStackSize(), fertile ? "forbidden" : "unforbidden"); } } } diff --git a/plugins/orders.cpp b/plugins/orders.cpp index 5891f924a3..174c645454 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -4,6 +4,7 @@ #include "PluginManager.h" #include "modules/Filesystem.h" +#include "modules/Job.h" #include "modules/Materials.h" #include "modules/World.h" @@ -44,8 +45,15 @@ DFHACK_PLUGIN("orders"); REQUIRE_GLOBAL(world); -static const std::string ORDERS_DIR = "dfhack-config/orders"; -static const std::string ORDERS_LIBRARY_DIR = "hack/data/orders"; +static const std::filesystem::path get_orders_dir() +{ + return Core::getInstance().getConfigPath() / "orders"; +} + +static std::filesystem::path get_orders_library_dir() +{ + return Core::getInstance().getHackPath() / "data" / "orders"; +} static command_result orders_command(color_ostream & out, std::vector & parameters); @@ -93,7 +101,7 @@ static command_result orders_command(color_ostream & out, std::vector files; - if (0 < Filesystem::listdir_recursive(ORDERS_LIBRARY_DIR, files, 0, false)) { + if (0 < Filesystem::listdir_recursive(get_orders_library_dir(), files, 0, false)) { // if the library directory doesn't exist, just skip it return; } @@ -163,7 +171,7 @@ static command_result orders_list_command(color_ostream & out) // support subdirs so we can identify and ignore subdirs with ".json" names. // also listdir_recursive will alphabetize the list for us. std::map files; - Filesystem::listdir_recursive(ORDERS_DIR, files, 0, false); + Filesystem::listdir_recursive(get_orders_dir(), files, 0, false); for (auto& it : files) { if (it.second) @@ -376,6 +384,7 @@ static command_result orders_export_command(color_ostream & out, const std::stri order["art"] = art; } + order["name"] = Job::getManagerOrderName(it); order["amount_left"] = it->amount_left; order["amount_total"] = it->amount_total; order["is_validated"] = bool(it->status.bits.validated); @@ -504,9 +513,9 @@ static command_result orders_export_command(color_ostream & out, const std::stri orders.append(order); } - Filesystem::mkdir(ORDERS_DIR); + Filesystem::mkdir(get_orders_dir()); - std::ofstream file(ORDERS_DIR + "/" + name + ".json"); + std::ofstream file(get_orders_dir() / ( name + ".json")); file << orders << std::endl; @@ -924,8 +933,7 @@ static command_result orders_import_command(color_ostream & out, const std::stri return CR_WRONG_USAGE; } - const std::string filename((is_library ? ORDERS_LIBRARY_DIR : ORDERS_DIR) + - "/" + fname + ".json"); + auto filename((is_library ? get_orders_library_dir() : get_orders_dir()) / (fname + ".json")); Json::Value orders; { diff --git a/plugins/overlay.cpp b/plugins/overlay.cpp index 580ca3fec6..85e5cce882 100644 --- a/plugins/overlay.cpp +++ b/plugins/overlay.cpp @@ -57,7 +57,7 @@ static int32_t interfacePct = 100; static void overlay_interpose_lua(const char *fn_name, int nargs = 0, int nres = 0, Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA, Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) { - DEBUG(event).print("calling overlay lua function: '%s'\n", fn_name); + DEBUG(event).print("calling overlay lua function: '{}'\n", fn_name); color_ostream & out = Core::getInstance().getConsole(); auto L = DFHack::Core::getInstance().getLuaState(); @@ -159,7 +159,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { Lua::CallLuaModuleFunction(out, "plugins.overlay", "rescan"); } - DEBUG(control).print("%sing interpose hooks\n", enable ? "enabl" : "disabl"); + DEBUG(control).print("{}ing interpose hooks\n", enable ? "enabl" : "disabl"); if (INTERPOSE_HOOKS_FAILED(adopt_region) || // INTERPOSE_HOOKS_FAILED(adventure_log) || diff --git a/plugins/pathable.cpp b/plugins/pathable.cpp index 3bf9e2c010..3fd1eaaad1 100644 --- a/plugins/pathable.cpp +++ b/plugins/pathable.cpp @@ -1,3 +1,5 @@ +#include + #include "Debug.h" #include "Error.h" #include "PluginManager.h" @@ -40,7 +42,7 @@ namespace DFHack { static std::vector textures; DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - textures = Textures::loadTileset("hack/data/art/pathable.png", 32, 32, true); + textures = Textures::loadTileset(Core::getInstance().getHackPath() / "data" / "art" / "pathable.png", 32, 32, true); return CR_OK; } @@ -84,20 +86,20 @@ static void paint_screen(const PaintCtx & ctx, const unordered_set & continue; } - DEBUG(log).print("scanning map tile at offset %d, %d\n", x, y); + DEBUG(log).print("scanning map tile at offset {}, {}\n", x, y); Screen::Pen cur_tile = Screen::readTile(x, y, true); - DEBUG(log).print("tile data: ch=%d, fg=%d, bg=%d, bold=%s\n", + DEBUG(log).print("tile data: ch={}, fg={}, bg={}, bold={}\n", cur_tile.ch, cur_tile.fg, cur_tile.bg, cur_tile.bold ? "true" : "false"); - DEBUG(log).print("tile data: tile=%d, tile_mode=%d, tile_fg=%d, tile_bg=%d\n", - cur_tile.tile, cur_tile.tile_mode, cur_tile.tile_fg, cur_tile.tile_bg); + DEBUG(log).print("tile data: tile={}, tile_mode={}, tile_fg={}, tile_bg={}\n", + cur_tile.tile, static_cast(cur_tile.tile_mode), cur_tile.tile_fg, cur_tile.tile_bg); if (!cur_tile.valid()) { - DEBUG(log).print("cannot read tile at offset %d, %d\n", x, y); + DEBUG(log).print("cannot read tile at offset {}, {}\n", x, y); continue; } bool can_walk = get_can_walk(map_pos); - DEBUG(log).print("tile is %swalkable at offset %d, %d\n", + DEBUG(log).print("tile is {}walkable at offset {}, {}\n", can_walk ? "" : "not ", x, y); if (ctx.use_graphics) { @@ -146,7 +148,7 @@ static bool get_depot_coords(color_ostream &out, unordered_set * depo depot_coords->clear(); for (auto bld : world->buildings.other.TRADE_DEPOT){ - DEBUG(log,out).print("found depot at (%d, %d, %d)\n", bld->centerx, bld->centery, bld->z); + DEBUG(log,out).print("found depot at ({}, {}, {})\n", bld->centerx, bld->centery, bld->z); depot_coords->emplace(bld->centerx, bld->centery, bld->z); } @@ -161,7 +163,7 @@ static bool get_pathability_groups(color_ostream &out, unordered_set * for (auto pos : depot_coords) { auto wgroup = Maps::getWalkableGroup(pos); if (wgroup) { - DEBUG(log,out).print("walkability group at (%d, %d, %d) is %d\n", pos.x, pos.y, pos.z, wgroup); + DEBUG(log,out).print("walkability group at ({}, {}, {}) is {}\n", pos.x, pos.y, pos.z, wgroup); depot_pathability_groups->emplace(wgroup); } } @@ -384,7 +386,7 @@ static bool wagon_flood(color_ostream &out, unordered_set * wagon_pat df::coord pos = ctx.search_edge.top(); ctx.search_edge.pop(); - TRACE(log,out).print("checking tile: (%d, %d, %d); pathability group: %d\n", pos.x, pos.y, pos.z, + TRACE(log,out).print("checking tile: ({}, {}, {}); pathability group: {}\n", pos.x, pos.y, pos.z, Maps::getWalkableGroup(pos)); if (entry_tiles.contains(pos)) { diff --git a/plugins/pet-uncapper.cpp b/plugins/pet-uncapper.cpp index 168c5f0bfd..0370015dcb 100644 --- a/plugins/pet-uncapper.cpp +++ b/plugins/pet-uncapper.cpp @@ -119,22 +119,22 @@ void impregnateMany(color_ostream &out, bool verbose = false) { } if (pregnancies || verbose) { - INFO(cycle, out).print("%d pet pregnanc%s initiated\n", + INFO(cycle, out).print("{} pet pregnanc{} initiated\n", pregnancies, pregnancies == 1 ? "y" : "ies"); } } command_result do_command(color_ostream &out, vector & parameters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } if (parameters.size() == 0 || parameters[0] == "status") { - out.print("%s is %s\n\n", plugin_name, is_enabled ? "enabled" : "not enabled"); - out.print("population cap per species: %d\n", config.get_int(CONFIG_POP_CAP)); - out.print("updating pregnancies every %d ticks\n", config.get_int(CONFIG_FREQ)); - out.print("pregancies last %d ticks\n", config.get_int(CONFIG_PREG_TIME)); + out.print("{} is {}\n\n", plugin_name, is_enabled ? "enabled" : "not enabled"); + out.print("population cap per species: {}\n", config.get_int(CONFIG_POP_CAP)); + out.print("updating pregnancies every {} ticks\n", config.get_int(CONFIG_FREQ)); + out.print("pregancies last {} ticks\n", config.get_int(CONFIG_PREG_TIME)); } else if (parameters[0] == "now") { impregnateMany(out, true); } else { @@ -165,19 +165,19 @@ DFhackCExport command_result plugin_init(color_ostream &out, vectorflags.is_set(plant_raw_flags::WET)) || (!is_watery && !p_raw->flags.is_set(plant_raw_flags::DRY))) { - out.printerr("Can't create plant: Plant type can't grow this %s water feature!\n" + out.printerr("Can't create plant: Plant type can't grow this {} water feature!\n" "Override with --force\n", is_watery ? "close to" : "far from"); return CR_FAILURE; } @@ -297,7 +297,7 @@ command_result df_grow(color_ostream &out, const cuboid &bounds, const plant_opt { if (!bounds.isValid()) { - out.printerr("Invalid cuboid! (%d:%d, %d:%d, %d:%d)\n", + out.printerr("Invalid cuboid! ({}:{}, {}:{}, {}:{})\n", bounds.x_min, bounds.x_max, bounds.y_min, bounds.y_max, bounds.z_min, bounds.z_max); return CR_FAILURE; } @@ -332,9 +332,9 @@ command_result df_grow(color_ostream &out, const cuboid &bounds, const plant_opt auto tt = Maps::getTileType(plant->pos); if (!tt || tileShape(*tt) != tiletype_shape::SAPLING) { - out.printerr("Invalid sapling tiletype at (%d, %d, %d): %s!\n", + out.printerr("Invalid sapling tiletype at ({}, {}, {}): {}\n", plant->pos.x, plant->pos.y, plant->pos.z, - tt ? ENUM_KEY_STR(tiletype, *tt).c_str() : "No map block!"); + tt ? ENUM_KEY_STR(tiletype, *tt) : "No map block!"); continue; // Bad tiletype } else if (*tt == tiletype::SaplingDead) @@ -349,9 +349,9 @@ command_result df_grow(color_ostream &out, const cuboid &bounds, const plant_opt } if (do_trees) - out.print("%d saplings and %d trees%s set to grow.\n", grown, grown_trees, options.dry_run ? " would be" : ""); + out.print("{} saplings and {} trees{} set to grow.\n", grown, grown_trees, options.dry_run ? " would be" : ""); else - out.print("%d saplings%s set to grow.\n", grown, options.dry_run ? " would be" : ""); + out.print("{} saplings{} set to grow.\n", grown, options.dry_run ? " would be" : ""); return CR_OK; } @@ -426,7 +426,7 @@ command_result df_removeplant(color_ostream &out, const cuboid &bounds, const pl { if (!bounds.isValid()) { - out.printerr("Invalid cuboid! (%d:%d, %d:%d, %d:%d)\n", + out.printerr("Invalid cuboid! ({}:{}, {}:{}, {}:{})\n", bounds.x_min, bounds.x_max, bounds.y_min, bounds.y_max, bounds.z_min, bounds.z_max); return CR_FAILURE; } @@ -473,9 +473,9 @@ command_result df_removeplant(color_ostream &out, const cuboid &bounds, const pl { if (tileShape(*tt) != tiletype_shape::SHRUB) { - out.printerr("Bad shrub tiletype at (%d, %d, %d): %s\n", + out.printerr("Bad shrub tiletype at ({}, {}, {}): {}\n", plant.pos.x, plant.pos.y, plant.pos.z, - ENUM_KEY_STR(tiletype, *tt).c_str()); + ENUM_KEY_STR(tiletype, *tt)); bad_tt = true; } } @@ -483,9 +483,9 @@ command_result df_removeplant(color_ostream &out, const cuboid &bounds, const pl { if (tileShape(*tt) != tiletype_shape::SAPLING) { - out.printerr("Bad sapling tiletype at (%d, %d, %d): %s\n", + out.printerr("Bad sapling tiletype at ({}, {}, {}): {}\n", plant.pos.x, plant.pos.y, plant.pos.z, - ENUM_KEY_STR(tiletype, *tt).c_str()); + ENUM_KEY_STR(tiletype, *tt)); bad_tt = true; } } @@ -493,7 +493,7 @@ command_result df_removeplant(color_ostream &out, const cuboid &bounds, const pl } else { - out.printerr("Bad plant tiletype at (%d, %d, %d): No map block!\n", + out.printerr("Bad plant tiletype at ({}, {}, {}): No map block!\n", plant.pos.x, plant.pos.y, plant.pos.z); bad_tt = true; } @@ -508,7 +508,7 @@ command_result df_removeplant(color_ostream &out, const cuboid &bounds, const pl if (!options.dry_run) { if (!uncat_plant(&plant)) - out.printerr("Remove plant: No block column at (%d, %d)!\n", plant.pos.x, plant.pos.y); + out.printerr("Remove plant: No block column at ({}, {})!\n", plant.pos.x, plant.pos.y); if (!bad_tt) // TODO: trees set_tt(plant.pos); @@ -518,7 +518,7 @@ command_result df_removeplant(color_ostream &out, const cuboid &bounds, const pl } } - out.print("Plants%s removed: %d (%d bad)\n", options.dry_run ? " that would be" : "", count, count_bad); + out.print("Plants{} removed: {} ({} bad)\n", options.dry_run ? " that would be" : "", count, count_bad); return CR_OK; } @@ -538,11 +538,11 @@ command_result df_plant(color_ostream &out, vector ¶meters) { // Print all non-grass raw IDs ("plant list") out.print("--- Shrubs ---\n"); for (auto p_raw : world->raws.plants.bushes) - out.print("%d: %s\n", p_raw->index, p_raw->id.c_str()); + out.print("{}: {}\n", p_raw->index, p_raw->id); out.print("\n--- Saplings ---\n"); for (auto p_raw : world->raws.plants.trees) - out.print("%d: %s\n", p_raw->index, p_raw->id.c_str()); + out.print("{}: {}\n", p_raw->index, p_raw->id); return CR_OK; } @@ -574,7 +574,7 @@ command_result df_plant(color_ostream &out, vector ¶meters) return CR_FAILURE; } - DEBUG(log, out).print("pos_1 = (%d, %d, %d)\npos_2 = (%d, %d, %d)\n", + DEBUG(log, out).print("pos_1 = ({}, {}, {})\npos_2 = ({}, {}, {})\n", pos_1.x, pos_1.y, pos_1.z, pos_2.x, pos_2.y, pos_2.z); if (!Core::getInstance().isMapLoaded()) @@ -599,7 +599,7 @@ command_result df_plant(color_ostream &out, vector ¶meters) if (!pos_1.isValid()) { // Attempt to use cursor for pos if active Gui::getCursorCoords(pos_1); - DEBUG(log, out).print("Try to use cursor (%d, %d, %d) for pos_1.\n", + DEBUG(log, out).print("Try to use cursor ({}, {}, {}) for pos_1.\n", pos_1.x, pos_1.y, pos_1.z); if (!pos_1.isValid()) @@ -609,20 +609,20 @@ command_result df_plant(color_ostream &out, vector ¶meters) } } - DEBUG(log, out).print("plant_idx = %d\n", options.plant_idx); + DEBUG(log, out).print("plant_idx = {}\n", options.plant_idx); auto p_raw = vector_get(world->raws.plants.all, options.plant_idx); if (p_raw) { - DEBUG(log, out).print("Plant raw: %s\n", p_raw->id.c_str()); + DEBUG(log, out).print("Plant raw: {}\n", p_raw->id); if (p_raw->flags.is_set(plant_raw_flags::GRASS)) { - out.printerr("Plant raw was grass: %d (%s)\n", options.plant_idx, p_raw->id.c_str()); + out.printerr("Plant raw was grass: {} ({})\n", options.plant_idx, p_raw->id); return CR_FAILURE; } } else { - out.printerr("Plant raw not found for create: %d\n", options.plant_idx); + out.printerr("Plant raw not found for create: {}\n", options.plant_idx); return CR_FAILURE; } } @@ -638,24 +638,24 @@ command_result df_plant(color_ostream &out, vector ¶meters) for (auto idx : filter) { - DEBUG(log, out).print("Filter/exclude test idx: %d\n", idx); + DEBUG(log, out).print("Filter/exclude test idx: {}\n", idx); auto p_raw = vector_get(world->raws.plants.all, idx); if (p_raw) { - DEBUG(log, out).print("Filter/exclude raw: %s\n", p_raw->id.c_str()); + DEBUG(log, out).print("Filter/exclude raw: {}\n", p_raw->id); if (p_raw->flags.is_set(plant_raw_flags::GRASS)) { - out.printerr("Filter/exclude plant raw was grass: %d (%s)\n", idx, p_raw->id.c_str()); + out.printerr("Filter/exclude plant raw was grass: {} ({})\n", idx, p_raw->id); return CR_FAILURE; } else if (options.grow && !p_raw->flags.is_set(plant_raw_flags::TREE)) { // User might copy-paste filters between grow and remove, so just log this - DEBUG(log, out).print("Filter/exclude shrub with grow: %d (%s)\n", idx, p_raw->id.c_str()); + DEBUG(log, out).print("Filter/exclude shrub with grow: {} ({})\n", idx, p_raw->id); } } else { - out.printerr("Plant raw not found for filter/exclude: %d\n", idx); + out.printerr("Plant raw not found for filter/exclude: {}\n", idx); return CR_FAILURE; } } @@ -689,12 +689,12 @@ command_result df_plant(color_ostream &out, vector ¶meters) bounds.addPos(world->map.x_count-1, world->map.y_count-1, 0); } - DEBUG(log, out).print("bounds = (%d:%d, %d:%d, %d:%d)\n", + DEBUG(log, out).print("bounds = ({}:{}, {}:{}, {}:{})\n", bounds.x_min, bounds.x_max, bounds.y_min, bounds.y_max, bounds.z_min, bounds.z_max); if (!bounds.isValid()) { - out.printerr("Invalid cuboid! (%d:%d, %d:%d, %d:%d)\n", + out.printerr("Invalid cuboid! ({}:{}, {}:{}, {}:{})\n", bounds.x_min, bounds.x_max, bounds.y_min, bounds.y_max, bounds.z_min, bounds.z_max); return CR_FAILURE; } diff --git a/plugins/preserve-rooms.cpp b/plugins/preserve-rooms.cpp index ae8337859d..eca7b888de 100644 --- a/plugins/preserve-rooms.cpp +++ b/plugins/preserve-rooms.cpp @@ -73,7 +73,7 @@ static void on_new_active_unit(color_ostream& out, void* data); static void do_cycle(color_ostream &out); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(control,out).print("initializing %s\n", plugin_name); + DEBUG(control,out).print("initializing {}\n", plugin_name); commands.push_back(PluginCommand( plugin_name, "Manage room assignments for off-map units and noble roles.", @@ -92,12 +92,12 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { EventManager::unregisterAll(plugin_self); } - DEBUG(control, out).print("now %s\n", is_enabled ? "enabled" : "disabled"); + DEBUG(control, out).print("now {}\n", is_enabled ? "enabled" : "disabled"); return CR_OK; } DFhackCExport command_result plugin_shutdown(color_ostream &out) { - DEBUG(control,out).print("shutting down %s\n", plugin_name); + DEBUG(control,out).print("shutting down {}\n", plugin_name); return CR_OK; } @@ -265,7 +265,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { static command_result do_command(color_ostream &out, vector ¶meters) { if (!World::isFortressMode() || !Core::getInstance().isMapLoaded()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -344,17 +344,17 @@ static void assign_nobles(color_ostream &out) { zone->spec_sub_flag.bits.active = true; Buildings::setOwner(zone, unit); assigned = true; - INFO(cycle,out).print("preserve-rooms: assigning %s to a %s-associated %s\n", - DF2CONSOLE(Units::getReadableName(unit)).c_str(), - toLower_cp437(code).c_str(), - ENUM_KEY_STR(civzone_type, zone->type).c_str()); + INFO(cycle,out).print("preserve-rooms: assigning {} to a {}-associated {}\n", + DF2CONSOLE(Units::getReadableName(unit)), + toLower_cp437(code), + ENUM_KEY_STR(civzone_type, zone->type)); break; } if (assigned) break; } if (!assigned && (zone->spec_sub_flag.bits.active || zone->assigned_unit_id != -1)) { - DEBUG(cycle,out).print("noble zone now reserved for eventual office holder: %d\n", zone_id); + DEBUG(cycle,out).print("noble zone now reserved for eventual office holder: {}\n", zone_id); zone->spec_sub_flag.bits.active = false; Buildings::setOwner(zone, NULL); } @@ -403,8 +403,8 @@ static void scrub_reservations(color_ostream &out) { continue; } } - DEBUG(cycle,out).print("removed reservation for dead, culled, or non-army hfid %d: %s\n", hfid, - hf ? DF2CONSOLE(Units::getReadableName(hf)).c_str() : "culled"); + DEBUG(cycle,out).print("removed reservation for dead, culled, or non-army hfid {}: {}\n", hfid, + hf ? DF2CONSOLE(Units::getReadableName(hf)) : "culled"); hfids_to_scrub.push_back(hfid); for (int32_t zone_id : zone_ids) { if (scrub_id_from_entries(hfid, zone_id, reserved_zones)) { @@ -461,9 +461,9 @@ static void handle_missing_assignments(color_ostream &out, if (spouse_hf && share_with_spouse) { if (spouse && Units::isActive(spouse) && !Units::isDead(spouse) && active_unit_ids.contains(spouse->id)) { - DEBUG(cycle,out).print("assigning zone %d (%s) to spouse %s\n", - zone_id, ENUM_KEY_STR(civzone_type, zone->type).c_str(), - DF2CONSOLE(Units::getReadableName(spouse)).c_str()); + DEBUG(cycle,out).print("assigning zone {} ({}) to spouse {}\n", + zone_id, ENUM_KEY_STR(civzone_type, zone->type), + DF2CONSOLE(Units::getReadableName(spouse))); Buildings::setOwner(zone, spouse); continue; } @@ -471,22 +471,22 @@ static void handle_missing_assignments(color_ostream &out, if (hf->died_year > -1) continue; // register the hf ids for reassignment and reserve the room - DEBUG(cycle,out).print("registering primary unit for reassignment to zone %d (%s): %d %s\n", - zone_id, ENUM_KEY_STR(civzone_type, zone->type).c_str(), hf->unit_id, - DF2CONSOLE(Units::getReadableName(hf)).c_str()); + DEBUG(cycle,out).print("registering primary unit for reassignment to zone {} ({}): {} {}\n", + zone_id, ENUM_KEY_STR(civzone_type, zone->type), hf->unit_id, + DF2CONSOLE(Units::getReadableName(hf))); pending_reassignment[hfid].push_back(zone_id); reserved_zones[zone_id].push_back(hfid); if (share_with_spouse && spouse) { - DEBUG(cycle,out).print("registering spouse unit for reassignment to zone %d (%s): hfid=%d\n", - zone_id, ENUM_KEY_STR(civzone_type, zone->type).c_str(), spouse_hfid); + DEBUG(cycle,out).print("registering spouse unit for reassignment to zone {} ({}): hfid={}\n", + zone_id, ENUM_KEY_STR(civzone_type, zone->type), spouse_hfid); pending_reassignment[spouse_hfid].push_back(zone_id); reserved_zones[zone_id].push_back(spouse_hfid); } - INFO(cycle,out).print("preserve-rooms: reserving %s for the return of %s%s%s\n", - toLower_cp437(ENUM_KEY_STR(civzone_type, zone->type)).c_str(), - DF2CONSOLE(Units::getReadableName(hf)).c_str(), + INFO(cycle,out).print("preserve-rooms: reserving {} for the return of {}{}\n", + toLower_cp437(ENUM_KEY_STR(civzone_type, zone->type)), + DF2CONSOLE(Units::getReadableName(hf)), spouse_hf ? " or their spouse, " : "", - spouse_hf ? DF2CONSOLE(Units::getReadableName(spouse_hf)).c_str() : ""); + spouse_hf ? DF2CONSOLE(Units::getReadableName(spouse_hf)) : ""); zone->spec_sub_flag.bits.active = false; } @@ -504,7 +504,7 @@ static void process_rooms(color_ostream &out, for (auto zone : vec) { auto idx = linear_index(df::global::world->buildings.all, (df::building*)(zone)); if (idx == -1) { - WARN(cycle, out).print("invalid building pointer %p in building vector\n", zone); + WARN(cycle, out).print("invalid building pointer {} in building vector\n", static_cast(zone)); continue; } if (zone->assigned_unit_id == -1) { @@ -513,7 +513,7 @@ static void process_rooms(color_ostream &out, } auto owner = Buildings::getOwner(zone); if (!owner) { - DEBUG(cycle, out).print("building %d has owner id %d but no such unit exists\n", zone->id, zone->assigned_unit_id); + DEBUG(cycle, out).print("building {} has owner id {} but no such unit exists\n", zone->id, zone->assigned_unit_id); continue; } auto hf = df::historical_figure::find(owner->hist_figure_id); @@ -530,7 +530,7 @@ static void process_rooms(color_ostream &out, static void do_cycle(color_ostream &out) { cycle_timestamp = world->frame_counter; - TRACE(cycle,out).print("running %s cycle\n", plugin_name); + TRACE(cycle,out).print("running {} cycle\n", plugin_name); if (config.get_bool(CONFIG_TRACK_MISSIONS)) { unordered_set active_unit_ids; @@ -542,7 +542,7 @@ static void do_cycle(color_ostream &out) { process_rooms(out, active_unit_ids, last_known_assignments_dining, world->buildings.other.ZONE_DINING_HALL); process_rooms(out, active_unit_ids, last_known_assignments_tomb, world->buildings.other.ZONE_TOMB, false); - DEBUG(cycle,out).print("tracking zone assignments: bedrooms: %zd, offices: %zd, dining halls: %zd, tombs: %zd\n", + DEBUG(cycle,out).print("tracking zone assignments: bedrooms: {}, offices: {}, dining halls: {}, tombs: {}\n", last_known_assignments_bedroom.size(), last_known_assignments_office.size(), last_known_assignments_dining.size(), last_known_assignments_tomb.size()); @@ -584,15 +584,15 @@ static void on_new_active_unit(color_ostream& out, void* data) { auto unit = df::unit::find(unit_id); if (!unit || unit->hist_figure_id < 0) return; - TRACE(event,out).print("unit %d (%s) arrived on map (hfid: %d, in pending: %d)\n", - unit->id, DF2CONSOLE(Units::getReadableName(unit)).c_str(), unit->hist_figure_id, + TRACE(event,out).print("unit {} ({}) arrived on map (hfid: {}, in pending: {})\n", + unit->id, DF2CONSOLE(Units::getReadableName(unit)), unit->hist_figure_id, pending_reassignment.contains(unit->hist_figure_id)); auto hfid = unit->hist_figure_id; auto it = pending_reassignment.find(hfid); if (it == pending_reassignment.end()) return; - INFO(event,out).print("preserve-rooms: restoring room ownership for %s\n", - DF2CONSOLE(Units::getReadableName(unit)).c_str()); + INFO(event,out).print("preserve-rooms: restoring room ownership for {}\n", + DF2CONSOLE(Units::getReadableName(unit))); for (auto zone_id : it->second) { reserved_zones.erase(zone_id); auto zone = virtual_cast(df::building::find(zone_id)); @@ -601,7 +601,7 @@ static void on_new_active_unit(color_ostream& out, void* data) { zone->spec_sub_flag.bits.active = true; if (zone->assigned_unit_id != -1 || spouse_has_sharable_room(out, hfid, zone->type)) continue; - DEBUG(event,out).print("reassigning zone %d\n", zone->id); + DEBUG(event,out).print("reassigning zone {}\n", zone->id); Buildings::setOwner(zone, unit); } pending_reassignment.erase(it); @@ -617,8 +617,8 @@ static void preserve_rooms_cycle(color_ostream &out) { } static bool preserve_rooms_setFeature(color_ostream &out, bool enabled, string feature) { - DEBUG(control,out).print("preserve_rooms_setFeature: enabled=%d, feature=%s\n", - enabled, feature.c_str()); + DEBUG(control,out).print("preserve_rooms_setFeature: enabled={}, feature={}\n", + enabled, feature); if (feature == "track-missions") { config.set_bool(CONFIG_TRACK_MISSIONS, enabled); if (is_enabled && enabled) @@ -633,7 +633,7 @@ static bool preserve_rooms_setFeature(color_ostream &out, bool enabled, string f } static bool preserve_rooms_getFeature(color_ostream &out, string feature) { - TRACE(control,out).print("preserve_rooms_getFeature: feature=%s\n", feature.c_str()); + TRACE(control,out).print("preserve_rooms_getFeature: feature={}\n", feature); if (feature == "track-missions") return config.get_bool(CONFIG_TRACK_MISSIONS); if (feature == "track-roles") @@ -642,7 +642,7 @@ static bool preserve_rooms_getFeature(color_ostream &out, string feature) { } static bool preserve_rooms_resetFeatureState(color_ostream &out, string feature) { - DEBUG(control,out).print("preserve_rooms_resetFeatureState: feature=%s\n", feature.c_str()); + DEBUG(control,out).print("preserve_rooms_resetFeatureState: feature={}\n", feature); if (feature == "track-missions") { vector zone_ids; std::transform(reserved_zones.begin(), reserved_zones.end(), std::back_inserter(zone_ids), [](auto & elem){ return elem.first; }); @@ -667,7 +667,7 @@ static int preserve_rooms_assignToRole(lua_State *L) { virtual_cast(df::building::find(zone_id)); if (!zone) return 0; - DEBUG(control,*out).print("preserve_rooms_assignToRole: zone_id=%d\n", zone->id); + DEBUG(control,*out).print("preserve_rooms_assignToRole: zone_id={}\n", zone->id); vector group_codes; Lua::GetVector(L, group_codes); @@ -689,7 +689,7 @@ static int preserve_rooms_assignToRole(lua_State *L) { static string get_role_assignment(color_ostream &out, df::building_civzonest * zone) { if (!zone) return ""; - TRACE(control,out).print("get_role_assignment: zone_id=%d\n", zone->id); + TRACE(control,out).print("get_role_assignment: zone_id={}\n", zone->id); auto it = noble_zones.find(zone->id); if (it == noble_zones.end() || it->second.empty()) return ""; @@ -709,7 +709,7 @@ static bool preserve_rooms_isReserved(color_ostream &out) { auto zone = Gui::getSelectedCivZone(out, true); if (!zone) return false; - TRACE(control,out).print("preserve_rooms_isReserved: zone_id=%d\n", zone->id); + TRACE(control,out).print("preserve_rooms_isReserved: zone_id={}\n", zone->id); auto it = reserved_zones.find(zone->id); return it != reserved_zones.end() && it->second.size() > 0; } @@ -718,7 +718,7 @@ static string preserve_rooms_getReservationName(color_ostream &out) { auto zone = Gui::getSelectedCivZone(out, true); if (!zone) return ""; - TRACE(control,out).print("preserve_rooms_getReservationName: zone_id=%d\n", zone->id); + TRACE(control,out).print("preserve_rooms_getReservationName: zone_id={}\n", zone->id); auto it = reserved_zones.find(zone->id); if (it != reserved_zones.end() && it->second.size() > 0) { if (auto hf = df::historical_figure::find(it->second.front())) { @@ -732,7 +732,7 @@ static bool preserve_rooms_clearReservation(color_ostream &out) { auto zone = Gui::getSelectedCivZone(out, true); if (!zone) return false; - DEBUG(control,out).print("preserve_rooms_clearReservation: zone_id=%d\n", zone->id); + DEBUG(control,out).print("preserve_rooms_clearReservation: zone_id={}\n", zone->id); clear_reservation(out, zone->id, zone); return true; } diff --git a/plugins/preserve-tombs.cpp b/plugins/preserve-tombs.cpp index 40b9eb56fd..a695b22e31 100644 --- a/plugins/preserve-tombs.cpp +++ b/plugins/preserve-tombs.cpp @@ -53,25 +53,25 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector & params) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot use %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot use {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } if (params.size() == 0 || params[0] == "status") { - out.print("%s is currently %s\n", plugin_name, is_enabled ? "enabled" : "disabled"); + out.print("{} is currently {}\n", plugin_name, is_enabled ? "enabled" : "disabled"); if (is_enabled) { out.print("tracked tomb assignments:\n"); std::for_each(tomb_assignments.begin(), tomb_assignments.end(), [&out](const auto& p){ auto& [unit_id, building_id] = p; auto* unit = df::unit::find(unit_id); std::string name = unit ? DF2CONSOLE(Units::getReadableName(unit)) : "UNKNOWN UNIT" ; - out.print("%s (id %d) -> building %d\n", name.c_str(), unit_id, building_id); + out.print("{} (id {}) -> building {}\n", name, unit_id, building_id); }); } return CR_OK; } if (params[0] == "now") { if (!is_enabled) { - out.printerr("Cannot update %s when not enabled", plugin_name); + out.printerr("Cannot update {} when not enabled", plugin_name); return CR_FAILURE; } update_tomb_assignments(out); @@ -96,13 +96,13 @@ static void do_disable() { DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } if (enable != is_enabled) { is_enabled = enable; - DEBUG(control,out).print("%s from the API; persisting\n", + DEBUG(control,out).print("{} from the API; persisting\n", is_enabled ? "enabled" : "disabled"); config.set_bool(CONFIG_IS_ENABLED, is_enabled); if (enable) @@ -110,7 +110,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { else do_disable(); } else { - DEBUG(control,out).print("%s from the API, but already %s; no action\n", + DEBUG(control,out).print("{} from the API, but already {}; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); } @@ -118,7 +118,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { } DFhackCExport command_result plugin_shutdown (color_ostream &out) { - DEBUG(control,out).print("shutting down %s\n", plugin_name); + DEBUG(control,out).print("shutting down {}\n", plugin_name); // PluginManager handles unregistering our handler from EventManager, // so we don't have to do that here @@ -136,7 +136,7 @@ DFhackCExport command_result plugin_load_site_data (color_ostream &out) { } is_enabled = config.get_bool(CONFIG_IS_ENABLED); - DEBUG(control,out).print("loading persisted enabled state: %s\n", + DEBUG(control,out).print("loading persisted enabled state: {}\n", is_enabled ? "true" : "false"); if (is_enabled) do_enable(out); @@ -146,7 +146,7 @@ DFhackCExport command_result plugin_load_site_data (color_ostream &out) { DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { if (event == DFHack::SC_WORLD_UNLOADED && is_enabled) { - DEBUG(control,out).print("world unloaded; disabling %s\n", + DEBUG(control,out).print("world unloaded; disabling {}\n", plugin_name); is_enabled = false; do_disable(); @@ -176,16 +176,16 @@ void onUnitDeath(color_ostream& out, void* ptr) { int32_t building_id = it->second; if (!assign_to_tomb(unit, building_id)) { if (unit) { - WARN(event, out).print("%s died - but failed to assign them back to their tomb %d\n", + WARN(event, out).print("{} died - but failed to assign them back to their tomb {}\n", DF2CONSOLE(Units::getReadableName(unit)).c_str(), building_id); } else { - WARN(event, out).print("Unit %d died - but failed to assign them back to their tomb %d\n", unit_id, building_id); + WARN(event, out).print("Unit {} died - but failed to assign them back to their tomb {}\n", unit_id, building_id); } return; } // success, print status update and remove assignment from our memo-list - INFO(event, out).print("%s died - assigning them back to their tomb\n", + INFO(event, out).print("{} died - assigning them back to their tomb\n", DF2CONSOLE(Units::getReadableName(unit)).c_str()); tomb_assignments.erase(it); } @@ -206,10 +206,10 @@ static void update_tomb_assignments(color_ostream &out) { if (it == tomb_assignments.end()) { tomb_assignments.emplace(tomb->assigned_unit_id, tomb->id); - DEBUG(cycle, out).print("%s new tomb assignment, unit %d to tomb %d\n", + DEBUG(cycle, out).print("{} new tomb assignment, unit {} to tomb {}\n", plugin_name, tomb->assigned_unit_id, tomb->id); } else if (it->second != tomb->id) { - DEBUG(cycle, out).print("%s tomb assignment to %d changed, (old: %d, new: %d)\n", + DEBUG(cycle, out).print("{} tomb assignment to {} changed, (old: {}, new: {})\n", plugin_name, tomb->assigned_unit_id, it->second, tomb->id); it->second = tomb->id; } @@ -221,17 +221,17 @@ static void update_tomb_assignments(color_ostream &out) { auto bld = df::building::find(building_id); if (!bld) { - DEBUG(cycle, out).print("%s tomb missing: %d - removing\n", plugin_name, building_id); + DEBUG(cycle, out).print("{} tomb missing: {} - removing\n", plugin_name, building_id); return true; } auto tomb = virtual_cast(bld); if (!tomb || !tomb->flags.bits.exists) { - DEBUG(cycle, out).print("%s tomb missing: %d - removing\n", plugin_name, building_id); + DEBUG(cycle, out).print("{} tomb missing: {} - removing\n", plugin_name, building_id); return true; } if (tomb->assigned_unit_id != unit_id) { - DEBUG(cycle, out).print("%s unit %d unassigned from tomb %d - removing\n", plugin_name, unit_id, building_id); + DEBUG(cycle, out).print("{} unit {} unassigned from tomb {} - removing\n", plugin_name, unit_id, building_id); return true; } diff --git a/plugins/probe.cpp b/plugins/probe.cpp index eff0c98c86..9df2200406 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -1,11 +1,16 @@ +#include +#include +#include + #include "LuaTools.h" +#include "MiscUtils.h" #include "PluginManager.h" #include "TileTypes.h" #include "modules/Gui.h" -#include "modules/Materials.h" #include "modules/MapCache.h" #include "modules/Maps.h" +#include "modules/Materials.h" #include "df/block_square_event_grassst.h" #include "df/block_square_event_world_constructionst.h" @@ -15,6 +20,8 @@ #include "df/civzone_type.h" #include "df/construction_type.h" #include "df/furnace_type.h" +#include "df/global_objects.h" +#include "df/inorganic_raw.h" #include "df/item.h" #include "df/map_block.h" #include "df/region_map_entry.h" @@ -41,7 +48,7 @@ static command_result df_cprobe(color_ostream &out, vector & parameters) if (!unit) return CR_FAILURE; - out.print("Creature %d, race %d (0x%x), civ %d (0x%x)\n", + out.print("Creature {}, race {} (0x{:x}), civ {} (0x{:x})\n", unit->id, unit->race, unit->race, unit->civ_id, unit->civ_id); for (auto inv_item : unit->inventory) { @@ -62,34 +69,32 @@ static command_result df_cprobe(color_ostream &out, vector & parameters) } static void describeTile(color_ostream &out, df::tiletype tiletype) { - out.print("%d", tiletype); + out.print("{}", static_cast(tiletype)); if (tileName(tiletype)) - out.print(" = %s", tileName(tiletype)); - out.print(" (%s)", ENUM_KEY_STR(tiletype, tiletype).c_str()); + out.print(" = {}", tileName(tiletype)); + out.print(" ({})", ENUM_KEY_STR(tiletype, tiletype).c_str()); out.print("\n"); df::tiletype_shape shape = tileShape(tiletype); df::tiletype_material material = tileMaterial(tiletype); df::tiletype_special special = tileSpecial(tiletype); df::tiletype_variant variant = tileVariant(tiletype); - out.print("%-10s: %4d %s\n","Class" ,shape, - ENUM_KEY_STR(tiletype_shape, shape).c_str()); - out.print("%-10s: %4d %s\n","Material" , - material, ENUM_KEY_STR(tiletype_material, material).c_str()); - out.print("%-10s: %4d %s\n","Special" , - special, ENUM_KEY_STR(tiletype_special, special).c_str()); - out.print("%-10s: %4d %s\n" ,"Variant" , - variant, ENUM_KEY_STR(tiletype_variant, variant).c_str()); - out.print("%-10s: %s\n" ,"Direction", + out.print("{:>10}: {:4} {}\n","Class" ,static_cast(shape), + ENUM_KEY_STR(tiletype_shape, shape)); + out.print("{:>10}: {:4} {}\n","Material" , static_cast(material), + ENUM_KEY_STR(tiletype_material, material)); + out.print("{:>10}: {:4} {}\n","Special" , static_cast(special), + ENUM_KEY_STR(tiletype_special, special)); + out.print("{:>10}: {:4} {}\n","Variant" , static_cast(variant), + ENUM_KEY_STR(tiletype_variant, variant)); + out.print("{:>10}: {}\n" ,"Direction", tileDirection(tiletype).getStr()); out.print("\n"); } static command_result df_probe(color_ostream &out, vector & parameters) { - DFHack::Materials *Materials = Core::getInstance().getMaterials(); - vector inorganic; - bool hasmats = Materials->CopyInorganicMaterials(inorganic); + auto& inorganic = world->raws.inorganics.all; if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); @@ -129,7 +134,7 @@ static command_result df_probe(color_ostream &out, vector & parameters) } auto &block = *b->getRaw(); - out.print("block addr: %p\n\n", &block); + out.print("block addr: {}\n\n", static_cast(&block)); df::tiletype tiletype = mc.tiletypeAt(cursor); df::tile_designation &des = block.designation[tileX][tileY]; @@ -143,8 +148,8 @@ static command_result df_probe(color_ostream &out, vector & parameters) out.print("base: "); describeTile(out, mc.baseTiletypeAt(cursor)); - out.print("temperature1: %d U\n", mc.temperature1At(cursor)); - out.print("temperature2: %d U\n", mc.temperature2At(cursor)); + out.print("temperature1: {} U\n", mc.temperature1At(cursor)); + out.print("temperature2: {} U\n", mc.temperature2At(cursor)); int offset = block.region_offset[des.bits.biome]; int bx = clip_range(block.region_pos.x + (offset % 3) - 1, 0, world->world_data->world_width-1); @@ -173,29 +178,25 @@ static command_result df_probe(color_ostream &out, vector & parameters) out << "geolayer: " << des.bits.geolayer_index << std::endl; int16_t base_rock = mc.layerMaterialAt(cursor); - if (base_rock != -1) { + if (base_rock != -1) + { out << "Layer material: " << std::dec << base_rock; - if(hasmats) - out << " / " << inorganic[base_rock].id - << " / " - << inorganic[base_rock].name - << std::endl; - else - out << std::endl; + out << " / " << inorganic[base_rock]->id + << " / " + << inorganic[base_rock]->material.stone_name + << std::endl; } int16_t vein_rock = mc.veinMaterialAt(cursor); - if (vein_rock != -1) { + if (vein_rock != -1) + { out << "Vein material (final): " << std::dec << vein_rock; - if(hasmats) - out << " / " << inorganic[vein_rock].id - << " / " - << inorganic[vein_rock].name - << " (" - << ENUM_KEY_STR(inclusion_type,b->veinTypeAt(cursor)) - << ")" - << std::endl; - else - out << std::endl; + out << " / " << inorganic[vein_rock]->id + << " / " + << inorganic[vein_rock]->material.stone_name + << " (" + << ENUM_KEY_STR(inclusion_type, b->veinTypeAt(cursor)) + << ")" + << std::endl; } MaterialInfo minfo(mc.baseMaterialAt(cursor)); if (minfo.isValid()) @@ -223,12 +224,12 @@ static command_result df_probe(color_ostream &out, vector & parameters) if (des.bits.water_stagnant) out << "stagnant" << std::endl; - #define PRINT_FLAG( FIELD, BIT ) out.print("%-16s= %c\n", #BIT , ( FIELD.bits.BIT ? 'Y' : ' ' ) ) + #define PRINT_FLAG( FIELD, BIT ) out.print("{:<16} = {}\n", #BIT , ( FIELD.bits.BIT ? 'Y' : ' ' ) ) - out.print("%-16s= %s\n", "dig", enum_item_key(des.bits.dig).c_str()); + out.print("{:<16} = {}\n", "dig", enum_item_key(des.bits.dig)); PRINT_FLAG(occ, dig_marked); PRINT_FLAG(occ, dig_auto); - out.print("%-16s= %s\n", "traffic", enum_item_key(des.bits.traffic).c_str()); + out.print("{:<16} = {}\n", "traffic", enum_item_key(des.bits.traffic)); PRINT_FLAG(occ, carve_track_north); PRINT_FLAG(occ, carve_track_south); PRINT_FLAG(occ, carve_track_east); @@ -241,7 +242,7 @@ static command_result df_probe(color_ostream &out, vector & parameters) PRINT_FLAG( des, water_table ); PRINT_FLAG( des, rained ); PRINT_FLAG( occ, monster_lair); - out.print("%-16s= %d\n", "fog_of_war", fog_of_war); + out.print("{:<16} = {}\n", "fog_of_war", fog_of_war); df::coord2d pc(blockX, blockY); @@ -251,19 +252,20 @@ static command_result df_probe(color_ostream &out, vector & parameters) PRINT_FLAG( des, feature_local ); if(local.type != -1) { - out.print("%-16s", ""); - out.print(" %4d", block.local_feature); - out.print(" (%2d)", local.type); - out.print(" addr %p ", local.origin); - out.print(" %s\n", sa_feature(local.type)); + out.print("{:<16}", ""); + out.print(" {:4}", block.local_feature); + out.print(" ({:2})", static_cast(local.type)); + out.print(" addr {}", static_cast(local.origin)); + out.print(" {}", sa_feature(local.type)); } PRINT_FLAG( des, feature_global ); if(global.type != -1) { - out.print("%-16s", ""); - out.print(" %4d", block.global_feature); - out.print(" (%2d)", global.type); - out.print(" %s\n", sa_feature(global.type)); + out.print("{:<16}", ""); + out.print(" {:4}", block.global_feature); + out.print(" ({:2})", static_cast(global.type)); + out.print(" {}", static_cast(global.origin)); + out.print(" {}", sa_feature(global.type)); } out << "local feature idx: " << block.local_feature << std::endl; @@ -272,7 +274,7 @@ static command_result df_probe(color_ostream &out, vector & parameters) out << std::endl; out << "Occupancy:" << std::endl; - out.print("%-16s= %s\n", "building", enum_item_key(occ.bits.building).c_str()); + out.print("{:<16} = {}\n", "building", enum_item_key(occ.bits.building)); PRINT_FLAG(occ, unit); PRINT_FLAG(occ, unit_grounded); PRINT_FLAG(occ, item); @@ -308,17 +310,6 @@ static command_result df_probe(color_ostream &out, vector & parameters) return CR_OK; } -union Subtype { - int16_t subtype; - df::civzone_type civzone_type; - df::furnace_type furnace_type; - df::workshop_type workshop_type; - df::construction_type construction_type; - df::shop_type shop_type; - df::siegeengine_type siegeengine_type; - df::trap_type trap_type; -}; - static command_result df_bprobe(color_ostream &out, vector & parameters) { auto bld = Gui::getSelectedBuilding(out); if (!bld) @@ -328,70 +319,70 @@ static command_result df_bprobe(color_ostream &out, vector & parameters) bld->getName(&name); auto bld_type = bld->getType(); - Subtype subtype{bld->getSubtype()}; + int16_t subtype{bld->getSubtype()}; int32_t custom = bld->getCustomType(); - out.print("Building %i, \"%s\", type %s (%i)", + out.print("Building {:<4}, \"{}\", type {} ({})", bld->id, - name.c_str(), - ENUM_KEY_STR(building_type, bld_type).c_str(), - bld_type); + name, + ENUM_KEY_STR(building_type, bld_type), + static_cast(bld_type)); switch (bld_type) { case df::building_type::Civzone: - out.print(", subtype %s (%i)", - ENUM_KEY_STR(civzone_type, subtype.civzone_type).c_str(), - subtype.subtype); + out.print(", subtype {} ({})", + ENUM_KEY_STR(civzone_type, static_cast(subtype)), + subtype); break; case df::building_type::Furnace: - out.print(", subtype %s (%i)", - ENUM_KEY_STR(furnace_type, subtype.furnace_type).c_str(), - subtype.subtype); - if (subtype.furnace_type == df::furnace_type::Custom) - out.print(", custom type %s (%i)", - world->raws.buildings.all[custom]->code.c_str(), + out.print(", subtype {} ({})", + ENUM_KEY_STR(furnace_type, static_cast(subtype)), + subtype); + if (static_cast(subtype) == df::furnace_type::Custom) + out.print(", custom type {} ({})", + world->raws.buildings.all[custom]->code, custom); break; case df::building_type::Workshop: - out.print(", subtype %s (%i)", - ENUM_KEY_STR(workshop_type, subtype.workshop_type).c_str(), - subtype.subtype); - if (subtype.workshop_type == df::workshop_type::Custom) - out.print(", custom type %s (%i)", - world->raws.buildings.all[custom]->code.c_str(), + out.print(", subtype {} ({})", + ENUM_KEY_STR(workshop_type, static_cast(subtype)), + subtype); + if (subtype == static_cast(df::workshop_type::Custom)) + out.print(", custom type {} ({})", + world->raws.buildings.all[custom]->code, custom); break; case df::building_type::Construction: - out.print(", subtype %s (%i)", - ENUM_KEY_STR(construction_type, subtype.construction_type).c_str(), - subtype.subtype); + out.print(", subtype {} ({})", + ENUM_KEY_STR(construction_type, static_cast(subtype)), + subtype); break; case df::building_type::Shop: - out.print(", subtype %s (%i)", - ENUM_KEY_STR(shop_type, subtype.shop_type).c_str(), - subtype.subtype); + out.print(", subtype {} ({})", + ENUM_KEY_STR(shop_type, static_cast(subtype)), + subtype); break; case df::building_type::SiegeEngine: - out.print(", subtype %s (%i)", - ENUM_KEY_STR(siegeengine_type, subtype.siegeengine_type).c_str(), - subtype.subtype); + out.print(", subtype {} ({})", + ENUM_KEY_STR(siegeengine_type, static_cast(subtype)), + subtype); break; case df::building_type::Trap: - out.print(", subtype %s (%i)", - ENUM_KEY_STR(trap_type, subtype.trap_type).c_str(), - subtype.subtype); + out.print(", subtype {} ({})", + ENUM_KEY_STR(trap_type, static_cast(subtype)), + subtype); break; case df::building_type::NestBox: { df::building_nest_boxst* nestbox = virtual_cast(bld); if (nestbox) - out.print(", claimed:(%i), items:%zu", nestbox->claimed_by, nestbox->contained_items.size()); + out.print(", claimed:({}), items:({})", nestbox->claimed_by, nestbox->contained_items.size()); break; } default: - if (subtype.subtype != -1) - out.print(", subtype %i", subtype.subtype); + if (subtype != -1) + out.print(", subtype {}", subtype); break; } diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index e8883fde59..5dd712e872 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -230,7 +230,7 @@ void printVeins(color_ostream &con, MatMap &mat_map, df::inorganic_raw *gloss = vector_get(inorganics, kv.first); if (!gloss) { - con.printerr("invalid material gloss: %hi\n", kv.first); + con.printerr("invalid material gloss: {}\n", kv.first); continue; } @@ -298,7 +298,7 @@ static df::world_region_details *get_details(df::world_data *data, df::coord2d p bool estimate_underground(color_ostream &out, EmbarkTileLayout &tile, df::world_region_details *details, int x, int y) { if (x < 0 || y < 0 || x > 15 || y > 15) { - out.printerr("Invalid embark coordinates: x=%i, y=%i\n", x, y); + out.printerr("Invalid embark coordinates: x={}, y={}\n", x, y); return false; } // Find actual biome @@ -428,7 +428,7 @@ bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &laye if (!geo_biome) { - out.printerr("Region geo-biome not found: (%d,%d)\n", + out.printerr("Region geo-biome not found: ({}, {})\n", tile.biome_pos.x, tile.biome_pos.y); return false; } @@ -610,9 +610,7 @@ static command_result embark_prospector(color_ostream &out, } if (options.hidden) { - DFHack::Materials *mats = Core::getInstance().getMaterials(); printVeins(out, veinMats, options); - mats->Finish(); } out << "Embark depth: " << (world_bottom.upper_z-world_bottom.lower_z+1) << " "; @@ -635,8 +633,6 @@ static command_result map_prospector(color_ostream &con, Maps::getSize(x_max, y_max, z_max); MapExtras::MapCache map; - DFHack::Materials *mats = Core::getInstance().getMaterials(); - DFHack::t_feature blockFeatureGlobal; DFHack::t_feature blockFeatureLocal; @@ -894,9 +890,6 @@ static command_result map_prospector(color_ostream &con, printMats(con, treeMats, world->raws.plants.all, options); } - // Cleanup - mats->Finish(); - return CR_OK; } diff --git a/plugins/regrass.cpp b/plugins/regrass.cpp index d40e1bc6dc..a17f5fb7e2 100644 --- a/plugins/regrass.cpp +++ b/plugins/regrass.cpp @@ -111,7 +111,7 @@ static bool valid_tile(color_ostream &out, regrass_options options, df::map_bloc else if (block->occupancy[tx][ty].bits.building > (options.buildings ? tile_building_occ::Passable : tile_building_occ::None)) { // Avoid stockpiles and planned/passable buildings unless enabled. - TRACE(log, out).print("Invalid tile: Building (%s)\n", + TRACE(log, out).print("Invalid tile: Building ({})\n", ENUM_KEY_STR(tile_building_occ, block->occupancy[tx][ty].bits.building).c_str()); return false; } @@ -180,7 +180,7 @@ static void grasses_for_tile(color_ostream &out, vector &vec, df::map_b for (auto p_raw : world->raws.plants.grasses) { // Sorted by df::plant_raw::index if (p_raw->flags.is_set(plant_raw_flags::BIOME_SUBTERRANEAN_WATER)) { - TRACE(log, out).print("Cave grass: %s\n", p_raw->id.c_str()); + TRACE(log, out).print("Cave grass: {}\n", p_raw->id); vec.push_back(p_raw->index); } } @@ -209,7 +209,7 @@ static void grasses_for_tile(color_ostream &out, vector &vec, df::map_b (evil || !flags.is_set(plant_raw_flags::EVIL)) && (savage || !flags.is_set(plant_raw_flags::SAVAGE))) { - TRACE(log, out).print("Surface grass: %s\n", p_raw->id.c_str()); + TRACE(log, out).print("Surface grass: {}\n", p_raw->id); vec.push_back(p_raw->index); } } @@ -241,7 +241,7 @@ static bool regrass_events(color_ostream &out, const regrass_options &options, d } else if (gr_ev->amount[tx][ty] > 0) { // Refill first non-zero grass. - DEBUG(log, out).print("Refilling existing grass (ID %d).\n", gr_ev->plant_index); + DEBUG(log, out).print("Refilling existing grass (ID {}).\n", gr_ev->plant_index); gr_ev->amount[tx][ty] = 100; return true; } @@ -263,12 +263,12 @@ static bool regrass_events(color_ostream &out, const regrass_options &options, d for (auto gr_ev : consider_grev) { // These grass events are already present. if (erase_from_vector(new_grasses, gr_ev->plant_index)) { - TRACE(log, out).print("Grass (ID %d) already present.\n", gr_ev->plant_index); + TRACE(log, out).print("Grass (ID {}) already present.\n", gr_ev->plant_index); } } for (auto id : new_grasses) { - DEBUG(log, out).print("Allocating new grass event (ID %d).\n", id); + DEBUG(log, out).print("Allocating new grass event (ID {}).\n", id); auto gr_ev = df::allocate(); if (!gr_ev) { out.printerr("Failed to allocate new grass event! Aborting loop.\n"); @@ -297,7 +297,7 @@ static bool regrass_events(color_ostream &out, const regrass_options &options, d for (size_t i = consider_grev.size(); i-- > 0;) { // Iterate backwards and remove invalid events from consideration. if (!vector_contains(valid_grasses, consider_grev[i]->plant_index)) { - TRACE(log, out).print("Removing grass (ID %d) from consideration.\n", + TRACE(log, out).print("Removing grass (ID {}) from consideration.\n", consider_grev[i]->plant_index); consider_grev.erase(consider_grev.begin() + i); } @@ -327,14 +327,14 @@ static bool regrass_events(color_ostream &out, const regrass_options &options, d if (options.max_grass) { // Don't need random choice. - DEBUG(log, out).print("Done with max regrass, %s.\n", + DEBUG(log, out).print("Done with max regrass, {}.\n", success ? "success" : "failure"); return success; } TRACE(log, out).print("Selecting random valid grass event...\n"); if (auto rand_grev = vector_get_random(consider_grev)) { - DEBUG(log, out).print("Refilling random grass (ID %d).\n", rand_grev->plant_index); + DEBUG(log, out).print("Refilling random grass (ID {}).\n", rand_grev->plant_index); rand_grev->amount[tx][ty] = 100; return true; } @@ -346,11 +346,11 @@ int regrass_tile(color_ostream &out, const regrass_options &options, df::map_blo { // Regrass single tile. Return 1 if tile success, else 0. CHECK_NULL_POINTER(block); if (!is_valid_tile_coord(df::coord2d(tx, ty))) { - out.printerr("(%d, %d) not in range 0-15!\n", tx, ty); + out.printerr("({}, {}) not in range 0-15!\n", tx, ty); return 0; } - DEBUG(log, out).print("Regrass tile (%d, %d, %d)\n", + DEBUG(log, out).print("Regrass tile ({}, {}, {})\n", block->map_pos.x + tx, block->map_pos.y + ty, block->map_pos.z); if (!regrass_events(out, options, block, tx, ty)) return 0; @@ -377,7 +377,7 @@ int regrass_tile(color_ostream &out, const regrass_options &options, df::map_blo { // Ramp or stairs. auto new_mat = (rand() & 1) ? tiletype_material::GRASS_LIGHT : tiletype_material::GRASS_DARK; auto new_tt = findTileType(shape, new_mat, tiletype_variant::NONE, tiletype_special::NONE, nullptr); - DEBUG(log, out).print("Tiletype to %s.\n", ENUM_KEY_STR(tiletype, new_tt).c_str()); + DEBUG(log, out).print("Tiletype to {}.\n", ENUM_KEY_STR(tiletype, new_tt)); block->tiletype[tx][ty] = new_tt; } return 1; @@ -385,12 +385,12 @@ int regrass_tile(color_ostream &out, const regrass_options &options, df::map_blo int regrass_cuboid(color_ostream &out, const regrass_options &options, const cuboid &bounds) { // Regrass all tiles in the defined cuboid. - DEBUG(log, out).print("Regrassing cuboid (%d:%d, %d:%d, %d:%d), clipped to map.\n", + DEBUG(log, out).print("Regrassing cuboid ({}:{}, {}:{}, {}:{})\n", bounds.x_min, bounds.x_max, bounds.y_min, bounds.y_max, bounds.z_min, bounds.z_max); int count = 0; bounds.forBlock([&](df::map_block *block, cuboid intersect) { - DEBUG(log, out).print("Cuboid regrass block at (%d, %d, %d)\n", + DEBUG(log, out).print("Cuboid regrass block at ({}, {}, {})\n", block->map_pos.x, block->map_pos.y, block->map_pos.z); intersect.forCoord([&](df::coord pos) { count += regrass_tile(out, options, block, pos.x&15, pos.y&15); @@ -404,7 +404,7 @@ int regrass_cuboid(color_ostream &out, const regrass_options &options, const cub int regrass_block(color_ostream &out, const regrass_options &options, df::map_block *block) { // Regrass all tiles in a single block. CHECK_NULL_POINTER(block); - DEBUG(log, out).print("Regrass block at (%d, %d, %d)\n", + DEBUG(log, out).print("Regrass block at ({}, {}, {})\n", block->map_pos.x, block->map_pos.y, block->map_pos.z); int count = 0; @@ -443,11 +443,11 @@ command_result df_regrass(color_ostream &out, vector ¶meters) else if (options.forced_plant == -2) { // Print all grass raw IDs. for (auto p_raw : world->raws.plants.grasses) - out.print("%d: %s\n", p_raw->index, p_raw->id.c_str()); + out.print("{}: {}\n", p_raw->index, p_raw->id); return CR_OK; } - DEBUG(log, out).print("pos_1 = (%d, %d, %d)\npos_2 = (%d, %d, %d)\n", + DEBUG(log, out).print("pos_1 = ({}, {}, {})\npos_2 = ({}, {}, {})\n", pos_1.x, pos_1.y, pos_1.z, pos_2.x, pos_2.y, pos_2.z); if (options.block && options.zlevel) { @@ -464,17 +464,17 @@ command_result df_regrass(color_ostream &out, vector ¶meters) } if (options.force) { - DEBUG(log, out).print("forced_plant = %d\n", options.forced_plant); + DEBUG(log, out).print("forced_plant = {}\n", options.forced_plant); auto p_raw = vector_get(world->raws.plants.all, options.forced_plant); if (p_raw) { if (!p_raw->flags.is_set(plant_raw_flags::GRASS)) { - out.printerr("Plant raw wasn't grass: %d (%s)\n", options.forced_plant, p_raw->id.c_str()); + out.printerr("Plant raw wasn't grass: {} ({})\n", options.forced_plant, p_raw->id); return CR_FAILURE; } - out.print("Forced grass: %s\n", p_raw->id.c_str()); + out.print("Forced grass: {}\n", p_raw->id); } else { - out.printerr("Plant raw not found for --force: %d\n", options.forced_plant); + out.printerr("Plant raw not found for --force: {}\n", options.forced_plant); return CR_FAILURE; } } @@ -484,7 +484,7 @@ command_result df_regrass(color_ostream &out, vector ¶meters) { // Specified z-levels or viewport z. auto z1 = pos_1.isValid() ? pos_1.z : Gui::getViewportPos().z; auto z2 = pos_2.isValid() ? pos_2.z : z1; - DEBUG(log, out).print("Regrassing z-levels %d to %d...\n", z1, z2); + DEBUG(log, out).print("Regrassing z-levels {} to {}...\n", z1, z2); count = regrass_cuboid(out, options, cuboid(0, 0, z1, world->map.x_count-1, world->map.y_count-1, z2)); } @@ -519,6 +519,6 @@ command_result df_regrass(color_ostream &out, vector ¶meters) DEBUG(log, out).print("Regrassing map...\n"); count = regrass_map(out, options); } - out.print("Regrew %d tiles of grass.\n", count); + out.print("Regrew {} tiles of grass.\n", count); return CR_OK; } diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp index ebf0359afd..62475ce1aa 100644 --- a/plugins/remotefortressreader/building_reader.cpp +++ b/plugins/remotefortressreader/building_reader.cpp @@ -560,18 +560,31 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re auto facing = actual->facing; switch (facing) { - case df::building_siegeenginest::Left: - remote_build->set_direction(WEST); - break; - case df::building_siegeenginest::Up: + using enum df::enums::siegeengine_orientation::siegeengine_orientation; + case North: remote_build->set_direction(NORTH); break; - case df::building_siegeenginest::Right: + case Northeast: + remote_build->set_direction(NORTHEAST); + break; + case East: remote_build->set_direction(EAST); break; - case df::building_siegeenginest::Down: + case Southeast: + remote_build->set_direction(SOUTHEAST); + break; + case South: remote_build->set_direction(SOUTH); break; + case Southwest: + remote_build->set_direction(SOUTHWEST); + break; + case West: + remote_build->set_direction(WEST); + break; + case Northwest: + remote_build->set_direction(NORTHWEST); + break; default: break; } diff --git a/plugins/remotefortressreader/proto/RemoteFortressReader.proto b/plugins/remotefortressreader/proto/RemoteFortressReader.proto index a092a12097..b7dc7bdc88 100644 --- a/plugins/remotefortressreader/proto/RemoteFortressReader.proto +++ b/plugins/remotefortressreader/proto/RemoteFortressReader.proto @@ -141,7 +141,11 @@ enum BuildingDirection EAST = 1; SOUTH = 2; WEST = 3; - NONE = 4; + NORTHEAST = 4; + SOUTHEAST = 5; + SOUTHWEST = 6; + NORTHWEST = 7; + NONE = 8; } enum TileDigDesignation diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 697eb3fe92..f8b54f01fb 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -213,7 +213,7 @@ command_result loadArtImageChunk(color_ostream &out, std::vector & { int index = atoi(parameters[0].c_str()); auto chunk = GetArtImageChunk(&(world->art_image_chunks.all), index); - out.print("Loaded chunk id: %d\n", chunk->id); + out.print("Loaded chunk id: {}\n", chunk->id); } return CR_OK; } @@ -304,15 +304,16 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) return CR_OK; } -uint16_t fletcher16(uint8_t const *data, size_t bytes) +uint16_t fletcher16(const void *data_, size_t bytes) { + auto data = static_cast(data_); uint16_t sum1 = 0xff, sum2 = 0xff; while (bytes) { size_t tlen = bytes > 20 ? 20 : bytes; bytes -= tlen; do { - sum2 += sum1 += *data++; + sum2 += sum1 += static_cast(*data++); } while (--tlen); sum1 = (sum1 & 0xff) + (sum1 >> 8); sum2 = (sum2 & 0xff) + (sum2 >> 8); @@ -335,7 +336,7 @@ void ConvertDfColor(int16_t index, RemoteFortressReader::ColorDefinition * out) out->set_blue(gps->uccolor[index][2]); } -void ConvertDfColor(int16_t in[3], RemoteFortressReader::ColorDefinition * out) +void ConvertDfColor(std::array& in, RemoteFortressReader::ColorDefinition * out) { int index = in[0] | (8 * in[2]); ConvertDfColor(index, out); @@ -623,11 +624,11 @@ static command_result CheckHashes(color_ostream &stream, const EmptyMessage *in) for (size_t i = 0; i < world->map.map_blocks.size(); i++) { df::map_block * block = world->map.map_blocks[i]; - fletcher16((uint8_t*)(block->tiletype), 16 * 16 * sizeof(df::enums::tiletype::tiletype)); + fletcher16((block->tiletype).data(), 16 * 16 * sizeof(df::enums::tiletype::tiletype)); } clock_t end = clock(); double elapsed_secs = double(end - start) / CLOCKS_PER_SEC; - stream.print("Checking all hashes took %f seconds.", elapsed_secs); + stream.print("Checking all hashes took {} seconds.", elapsed_secs); return CR_OK; } @@ -654,7 +655,7 @@ bool IsTiletypeChanged(DFCoord pos) uint16_t hash; df::map_block * block = Maps::getBlock(pos); if (block) - hash = fletcher16((uint8_t*)(block->tiletype), 16 * 16 * (sizeof(df::enums::tiletype::tiletype))); + hash = fletcher16((block->tiletype).data(), 16 * 16 * (sizeof(df::enums::tiletype::tiletype))); else hash = 0; if (hashes[pos] != hash) @@ -672,7 +673,7 @@ bool IsDesignationChanged(DFCoord pos) uint16_t hash; df::map_block * block = Maps::getBlock(pos); if (block) - hash = fletcher16((uint8_t*)(block->designation), 16 * 16 * (sizeof(df::tile_designation))); + hash = fletcher16((block->designation).data(), 16 * 16 * (sizeof(df::tile_designation))); else hash = 0; if (waterHashes[pos] != hash) @@ -1679,8 +1680,8 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques continue; } - using df::global::cur_year; - using df::global::cur_year_tick; + //using df::global::cur_year; + //using df::global::cur_year_tick; send_unit->set_age(Units::getAge(unit, false)); @@ -2262,8 +2263,7 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w out->set_map_y(pos_y); out->set_world_width(17); out->set_world_height(17); - char name[256]; - sprintf(name, "Region %d, %d", pos_x, pos_y); + std::string name = fmt::format("Region {}, {}", pos_x, pos_y); out->set_name_english(name); out->set_name(name); auto poles = worldData->flip_latitude; @@ -2366,8 +2366,7 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w int pos_y = worldRegionDetails->pos.y; out->set_map_x(pos_x); out->set_map_y(pos_y); - char name[256]; - sprintf(name, "Region %d, %d", pos_x, pos_y); + std::string name = fmt::format("Region {}, {}", pos_x, pos_y); out->set_name_english(name); out->set_name(name); diff --git a/plugins/rendermax/renderer_light.cpp b/plugins/rendermax/renderer_light.cpp index 469c849091..b905cd93c5 100644 --- a/plugins/rendermax/renderer_light.cpp +++ b/plugins/rendermax/renderer_light.cpp @@ -1185,12 +1185,12 @@ void lightingEngineViewscreen::loadSettings() int ret=luaL_loadfile(s,settingsfile.c_str()); if(ret==LUA_ERRFILE) { - out.printerr("File not found:%s\n",settingsfile.c_str()); + out.printerr("File not found:{}\n",settingsfile); lua_pop(s,1); } else if(ret==LUA_ERRSYNTAX) { - out.printerr("Syntax error:\n\t%s\n",lua_tostring(s,-1)); + out.printerr("Syntax error:\n\t{}\n",lua_tostring(s,-1)); } else { @@ -1202,38 +1202,38 @@ void lightingEngineViewscreen::loadSettings() lua_pushlightuserdata(s, this); lua_pushvalue(s,env); Lua::SafeCall(out,s,2,0); - out.print("%zu materials loaded\n",matDefs.size()); + out.print("{} materials loaded\n",matDefs.size()); lua_pushcfunction(s, parseSpecial); lua_pushlightuserdata(s, this); lua_pushvalue(s,env); Lua::SafeCall(out,s,2,0); - out.print("%zu day light colors loaded\n",dayColors.size()); + out.print("{} day light colors loaded\n",dayColors.size()); lua_pushcfunction(s, parseBuildings); lua_pushlightuserdata(s, this); lua_pushvalue(s,env); Lua::SafeCall(out,s,2,0); - out.print("%zu buildings loaded\n",buildingDefs.size()); + out.print("{} buildings loaded\n",buildingDefs.size()); lua_pushcfunction(s, parseCreatures); lua_pushlightuserdata(s, this); lua_pushvalue(s,env); Lua::SafeCall(out,s,2,0); - out.print("%zu creatures loaded\n",creatureDefs.size()); + out.print("{} creatures loaded\n",creatureDefs.size()); lua_pushcfunction(s, parseItems); lua_pushlightuserdata(s, this); lua_pushvalue(s,env); Lua::SafeCall(out,s,2,0); - out.print("%zu items loaded\n",itemDefs.size()); + out.print("{} items loaded\n",itemDefs.size()); } } } catch(std::exception& e) { - out.printerr("%s",e.what()); + out.printerr("{}\n",e.what()); } lua_pop(s,1); } diff --git a/plugins/rendermax/rendermax.cpp b/plugins/rendermax/rendermax.cpp index 5515bb39a2..39e59e4f6b 100644 --- a/plugins/rendermax/rendermax.cpp +++ b/plugins/rendermax/rendermax.cpp @@ -433,7 +433,7 @@ static command_result rendermax(color_ostream &out, vector & parameters else if(cmd=="disable") { if(current_mode==MODE_DEFAULT) - out.print("%s\n","Not installed, doing nothing."); + out.print("{}\n","Not installed, doing nothing."); else removeOld(); gps->force_full_display_count++; diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index 1feb72bbc6..e5bd2479f5 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -16,6 +16,7 @@ #include "df/map_block.h" #include "df/world.h" +#include #include using std::string; diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index 994ac067ac..7eecabd2c5 100644 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -69,7 +69,7 @@ static PersistentDataItem & ensure_seed_config(color_ostream &out, int id) { if (watched_seeds.count(id)) return watched_seeds[id]; string keyname = SEED_CONFIG_KEY_PREFIX + int_to_string(id); - DEBUG(control,out).print("creating new persistent key for seed type %d\n", id); + DEBUG(control,out).print("creating new persistent key for seed type {}\n", id); watched_seeds.emplace(id, World::GetPersistentSiteData(keyname, true)); watched_seeds[id].set_int(SEED_CONFIG_ID, id); return watched_seeds[id]; @@ -77,7 +77,7 @@ static PersistentDataItem & ensure_seed_config(color_ostream &out, int id) { static void remove_seed_config(color_ostream &out, int id) { if (!watched_seeds.count(id)) return; - DEBUG(control,out).print("removing persistent key for seed type %d\n", id); + DEBUG(control,out).print("removing persistent key for seed type {}\n", id); World::DeletePersistentData(watched_seeds[id]); watched_seeds.erase(id); } @@ -90,12 +90,12 @@ static bool validate_seed_config(color_ostream& out, PersistentDataItem c) int seed_id = c.get_int(SEED_CONFIG_ID); auto plant = df::plant_raw::find(seed_id); if (!plant) { - WARN(control,out).print("discarded invalid seed id: %d\n", seed_id); + WARN(control,out).print("discarded invalid seed id: {}\n", seed_id); return false; } bool valid = (!plant->flags.is_set(df::enums::plant_raw_flags::TREE)); if (!valid) { - DEBUG(control, out).print("invalid configuration for %s discarded\n", plant->id.c_str()); + DEBUG(control, out).print("invalid configuration for {} discarded\n", plant->id); } return valid; } @@ -108,7 +108,7 @@ static void do_cycle(color_ostream &out, int32_t *num_enabled_seeds = NULL, int3 static void seedwatch_setTarget(color_ostream &out, string name, int32_t num); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(control,out).print("initializing %s\n", plugin_name); + DEBUG(control,out).print("initializing {}\n", plugin_name); // provide a configuration interface for the plugin commands.push_back(PluginCommand( @@ -144,19 +144,19 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -304,7 +304,7 @@ static void scan_seeds(color_ostream &out, unordered_map *acce } static void do_cycle(color_ostream &out, int32_t *num_enabled_seed_types, int32_t *num_disabled_seed_types) { - DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + DEBUG(cycle,out).print("running {} cycle\n", plugin_name); // mark that we have recently run cycle_timestamp = world->frame_counter; @@ -325,14 +325,14 @@ static void do_cycle(color_ostream &out, int32_t *num_enabled_seed_types, int32_ if (accessible_counts[id] <= target && (Kitchen::isPlantCookeryAllowed(id) || Kitchen::isSeedCookeryAllowed(id))) { - DEBUG(cycle,out).print("disabling seed mat: %d\n", id); + DEBUG(cycle,out).print("disabling seed mat: {}\n", id); if (num_disabled_seed_types) ++*num_disabled_seed_types; Kitchen::denyPlantSeedCookery(id); } else if (target + TARGET_BUFFER < accessible_counts[id] && (!Kitchen::isPlantCookeryAllowed(id) || !Kitchen::isSeedCookeryAllowed(id))) { - DEBUG(cycle,out).print("enabling seed mat: %d\n", id); + DEBUG(cycle,out).print("enabling seed mat: {}\n", id); if (num_enabled_seed_types) ++*num_enabled_seed_types; Kitchen::allowPlantSeedCookery(id); @@ -351,7 +351,7 @@ static void set_target(color_ostream &out, int32_t id, int32_t target) { else { if (id < 0 || (size_t)id >= world->raws.plants.all.size()) { WARN(control,out).print( - "cannot set target for unknown plant id: %d\n", id); + "cannot set target for unknown plant id: {}\n", id); return; } PersistentDataItem &c = ensure_seed_config(out, id); @@ -385,7 +385,7 @@ static void seedwatch_setTarget(color_ostream &out, string name, int32_t num) { if (!world_plant_ids.count(token)) { token = toUpper_cp437(token); if (!world_plant_ids.count(token)) { - out.printerr("%s has not been found as a material.\n", token.c_str()); + out.printerr("{} has not been found as a material.\n", token); return; } } diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index 198dd190e0..977c767aeb 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -31,7 +31,7 @@ REQUIRE_GLOBAL(world); command_result df_showmood (color_ostream &out, vector & parameters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -65,7 +65,7 @@ command_result df_showmood (color_ostream &out, vector & parameters) out.printerr("Dwarf with strange mood does not have a mood type!\n"); continue; } - out.print("%s is currently ", DF2CONSOLE(out, Units::getReadableName(unit)).c_str()); + out.print("{} is currently ", DF2CONSOLE(out, Units::getReadableName(unit))); switch (unit->mood) { case mood_type::Macabre: @@ -143,7 +143,7 @@ command_result df_showmood (color_ostream &out, vector & parameters) out.print("do something else..."); break; } - out.print(" and become a legendary %s", ENUM_ATTR_STR(job_skill, caption_noun, unit->job.mood_skill)); + out.print(" and become a legendary {}", ENUM_ATTR_STR(job_skill, caption_noun, unit->job.mood_skill)); if (unit->mood == mood_type::Possessed) out.print(" (but not really)"); break; @@ -160,7 +160,7 @@ command_result df_showmood (color_ostream &out, vector & parameters) { string name; building->getName(&name); - out.print("claimed a %s and wants", name.c_str()); + out.print("claimed a {} and wants", name.c_str()); } else out.print("not yet claimed a workshop but will want"); @@ -169,7 +169,7 @@ command_result df_showmood (color_ostream &out, vector & parameters) for (size_t i = 0; i < job->job_items.elements.size(); i++) { df::job_item *item = job->job_items.elements[i]; - out.print("Item %zu: ", i + 1); + out.print("Item {}: ", i + 1); MaterialInfo matinfo(item->mat_type, item->mat_index); @@ -178,37 +178,37 @@ command_result df_showmood (color_ostream &out, vector & parameters) switch (item->item_type) { case item_type::BOULDER: - out.print("%s boulder", mat_name.c_str()); + out.print("{} boulder", mat_name); break; case item_type::BLOCKS: - out.print("%s blocks", mat_name.c_str()); + out.print("{} blocks", mat_name); break; case item_type::WOOD: - out.print("%s logs", mat_name.c_str()); + out.print("{} logs", mat_name); break; case item_type::BAR: if (matinfo.isInorganicWildcard()) mat_name = "metal"; if (matinfo.inorganic && matinfo.inorganic->flags.is_set(inorganic_flags::WAFERS)) - out.print("%s wafers", mat_name.c_str()); + out.print("{} wafers", mat_name); else - out.print("%s bars", mat_name.c_str()); + out.print("{} bars", mat_name); break; case item_type::SMALLGEM: - out.print("%s cut gems", mat_name.c_str()); + out.print("{} cut gems", mat_name); break; case item_type::ROUGH: if (matinfo.isAnyInorganic()) { if (matinfo.isInorganicWildcard()) mat_name = "any"; - out.print("%s rough gems", mat_name.c_str()); + out.print("{} rough gems", mat_name); } else - out.print("raw %s", mat_name.c_str()); + out.print("raw {}", mat_name); break; case item_type::SKIN_TANNED: - out.print("%s leather", mat_name.c_str()); + out.print("{} leather", mat_name); break; case item_type::CLOTH: if (matinfo.isNone()) @@ -220,50 +220,51 @@ command_result df_showmood (color_ostream &out, vector & parameters) else if (item->flags2.bits.yarn) mat_name = "any yarn"; } - out.print("%s cloth", mat_name.c_str()); + out.print("{} cloth", mat_name); break; case item_type::REMAINS: - out.print("%s remains", mat_name.c_str()); + out.print("{} remains", mat_name); break; case item_type::CORPSE: - out.print("%s %scorpse", mat_name.c_str(), (item->flags1.bits.murdered ? "murdered " : "")); + out.print("{} {}corpse", mat_name, (item->flags1.bits.murdered ? "murdered " : "")); break; case item_type::NONE: if (item->flags2.bits.body_part) { if (item->flags2.bits.bone) - out.print("%s bones", mat_name.c_str()); + out.print("{} bones", mat_name); else if (item->flags2.bits.shell) - out.print("%s shells", mat_name.c_str()); + out.print("{} shells", mat_name); else if (item->flags2.bits.horn) - out.print("%s horns", mat_name.c_str()); + out.print("{} horns", mat_name); else if (item->flags2.bits.pearl) - out.print("%s pearls", mat_name.c_str()); + out.print("{} pearls", mat_name); else if (item->flags2.bits.ivory_tooth) - out.print("%s ivory/teeth", mat_name.c_str()); + out.print("{} ivory/teeth", mat_name); else - out.print("%s unknown body parts (%s:%s:%s)", - mat_name.c_str(), - bitfield_to_string(item->flags1).c_str(), - bitfield_to_string(item->flags2).c_str(), - bitfield_to_string(item->flags3).c_str()); + out.print("{} unknown body parts ({}:{}:{})", + mat_name, + bitfield_to_string(item->flags1), + bitfield_to_string(item->flags2), + bitfield_to_string(item->flags3)); } else - out.print("indeterminate %s item (%s:%s:%s)", - mat_name.c_str(), - bitfield_to_string(item->flags1).c_str(), - bitfield_to_string(item->flags2).c_str(), - bitfield_to_string(item->flags3).c_str()); + out.print("indeterminate {} item ({}:{}:{})", + mat_name, + bitfield_to_string(item->flags1), + bitfield_to_string(item->flags2), + bitfield_to_string(item->flags3)); break; default: { ItemTypeInfo itinfo(item->item_type, item->item_subtype); - out.print("item %s material %s flags (%s:%s:%s)", - itinfo.toString().c_str(), mat_name.c_str(), - bitfield_to_string(item->flags1).c_str(), - bitfield_to_string(item->flags2).c_str(), - bitfield_to_string(item->flags3).c_str()); + out.print("item {} material {} flags ({}:{}:{})", + itinfo.toString(), + mat_name, + bitfield_to_string(item->flags1), + bitfield_to_string(item->flags2), + bitfield_to_string(item->flags3)); break; } } @@ -280,7 +281,7 @@ command_result df_showmood (color_ostream &out, vector & parameters) if (job->items[j]->job_item_idx == int32_t(i)) count_got += 1; } - out.print(", got %i of %i\n", count_got, + out.print(", got {} of {}\n", count_got, item->quantity < divisor ? item->quantity : item->quantity/divisor); } } diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index ca30fc76b4..3d9fbd537d 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -1503,7 +1503,7 @@ static void releaseTiredWorker(EngineInfo *engine, df::job *job, df::unit *worke if (Job::removeWorker(job)) { color_ostream_proxy out(Core::getInstance().getConsole()); - out.print("Released tired operator %d from siege engine.\n", worker->id); + out.print("Released tired operator {} from siege engine.\n", worker->id); if (process_jobs) *process_jobs = true; diff --git a/plugins/sort.cpp b/plugins/sort.cpp index add54688c4..f1b700c5af 100644 --- a/plugins/sort.cpp +++ b/plugins/sort.cpp @@ -1,3 +1,6 @@ +#include +#include + #include "Debug.h" #include "LuaTools.h" #include "PluginManager.h" @@ -11,6 +14,7 @@ #include "df/widget_unit_list.h" #include "df/world.h" + using std::vector; using std::string; @@ -25,8 +29,9 @@ namespace DFHack { DBG_DECLARE(sort, log, DebugCategory::LINFO); } -using item_or_unit = std::pair; -using filter_vec_type = std::vector>; +using item_or_unit = std::variant; +using filter_func = bool(item_or_unit); +using filter_vec_type = std::vector>; // recreated here since our autogenerated df::sort_entry lacks template params struct sort_entry { @@ -40,18 +45,9 @@ static const string DFHACK_SORT_IDENT = "dfhack_sort"; // filter logic // -static bool probing = false; -static bool probe_result = false; - static bool do_filter(const char *module_name, const char *fn_name, const item_or_unit &elem) { - if (elem.second) return true; - auto unit = (df::unit *)elem.first; - - if (probing) { - TRACE(log).print("probe successful\n"); - probe_result = true; - return false; - } + if (std::holds_alternative(elem)) return true; + auto unit = std::get(elem); bool ret = true; color_ostream &out = Core::getInstance().getConsole(); @@ -60,7 +56,7 @@ static bool do_filter(const char *module_name, const char *fn_name, const item_o ret = lua_toboolean(L, 1); } ); - TRACE(log).print("filter result for %s: %d\n", Units::getReadableName(unit).c_str(), ret); + TRACE(log).print("filter result for {}: {}\n", Units::getReadableName(unit), ret); return !ret; } @@ -76,35 +72,28 @@ static bool do_work_animal_assignment_filter(item_or_unit elem) { return do_filter("plugins.sort.info", "do_work_animal_assignment_filter", elem); } -static int32_t our_filter_idx(df::widget_unit_list *unitlist) { - if (world->units.active.empty()) - return true; - - df::unit *u = world->units.active[0]; // any unit will do; we just need a sentinel - if (!u) - return true; - - probing = true; - probe_result = false; +static int32_t our_filter_idx(filter_func* filter, df::widget_unit_list* unitlist) +{ int32_t idx = 0; - filter_vec_type *filter_vec = reinterpret_cast(&unitlist->filter_func); + filter_vec_type* filter_vec = reinterpret_cast(&unitlist->filter_func); TRACE(log).print("probing for our filter function\n"); - for (auto fn : *filter_vec) { - fn(std::make_pair(u, false)); - if (probe_result) { - TRACE(log).print("found our filter function at idx %d\n", idx); - break; + + for (auto& fn : *filter_vec) + { + auto t = fn.target(); + if (t && *t == filter) + { + TRACE(log).print("found our filter function at idx {}\n", idx); + return idx; } ++idx; } - - probing = false; - return probe_result ? idx : -1; + return -1; } -static df::widget_unit_list * get_squad_unit_list() { +static df::widget_unit_list* get_squad_unit_list() { return virtual_cast( Gui::getWidget(&game->main_interface.unit_selector, "Unit selector")); } @@ -144,13 +133,13 @@ static df::widget_unit_list * get_work_animal_assignment_unit_list() { // static bool sort_proxy(const item_or_unit &a, const item_or_unit &b) { - if (a.second || b.second) + if (std::holds_alternative(a) || std::holds_alternative(b)) return true; bool ret = true; color_ostream &out = Core::getInstance().getConsole(); Lua::CallLuaModuleFunction(out, "plugins.sort", "do_sort", - std::make_tuple((df::unit *)a.first, (df::unit *)b.first), + std::make_tuple(std::get(a), std::get(b)), 1, [&](lua_State *L){ ret = lua_toboolean(L, 1); } @@ -180,10 +169,10 @@ DFhackCExport command_result plugin_init(color_ostream &out, vector= 0) { - DEBUG(log,out).print("removing %s filter function\n", which); + DEBUG(log,out).print("removing {} filter function\n", which); filter_vec_type *filter_vec = reinterpret_cast(&unitlist->filter_func); vector_erase_at(*filter_vec, idx); } @@ -193,29 +182,29 @@ static void remove_sort_function(color_ostream &out, const char *which, df::widg std::vector *sorting_by = reinterpret_cast *>(&unitlist->sorting_by); int32_t idx = our_sort_idx(*sorting_by); if (idx >= 0) { - DEBUG(log).print("removing %s sort function\n", which); + DEBUG(log).print("removing {} sort function\n", which); vector_erase_at(*sorting_by, idx); } } DFhackCExport command_result plugin_shutdown(color_ostream &out) { if (auto unitlist = get_squad_unit_list()) { - remove_filter_function(out, "squad", unitlist); + remove_filter_function(out, do_squad_filter, "squad", unitlist); remove_sort_function(out, "squad", unitlist); } if (auto unitlist = get_interrogate_unit_list("Open cases")) - remove_filter_function(out, "open cases interrogate", unitlist); + remove_filter_function(out, do_justice_filter, "open cases interrogate", unitlist); if (auto unitlist = get_interrogate_unit_list("Cold cases")) - remove_filter_function(out, "cold cases interrogate", unitlist); + remove_filter_function(out, do_justice_filter, "cold cases interrogate", unitlist); if (auto unitlist = get_convict_unit_list("Open cases")) - remove_filter_function(out, "open cases convict", unitlist); + remove_filter_function(out, do_justice_filter, "open cases convict", unitlist); if (auto unitlist = get_convict_unit_list("Cold cases")) - remove_filter_function(out, "cold cases convict", unitlist); + remove_filter_function(out, do_justice_filter, "cold cases convict", unitlist); if (auto unitlist = get_work_animal_assignment_unit_list()) - remove_filter_function(out, "work animal assignment", unitlist); + remove_filter_function(out, do_work_animal_assignment_filter, "work animal assignment", unitlist); return CR_OK; } @@ -226,7 +215,7 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out) { static void sort_set_squad_filter_fn(color_ostream &out) { auto unitlist = get_squad_unit_list(); - if (unitlist && our_filter_idx(unitlist) == -1) { + if (unitlist && our_filter_idx(do_squad_filter, unitlist) == -1) { DEBUG(log).print("adding squad filter function\n"); auto filter_vec = reinterpret_cast(&unitlist->filter_func); filter_vec->emplace_back(do_squad_filter); @@ -238,7 +227,7 @@ static void sort_set_squad_filter_fn(color_ostream &out) { } static void sort_set_justice_filter_fn(color_ostream &out, df::widget_unit_list *unitlist) { - if (unitlist && our_filter_idx(unitlist) == -1) { + if (unitlist && our_filter_idx(do_justice_filter, unitlist) == -1) { DEBUG(log).print("adding justice filter function\n"); auto filter_vec = reinterpret_cast(&unitlist->filter_func); filter_vec->emplace_back(do_justice_filter); @@ -247,7 +236,7 @@ static void sort_set_justice_filter_fn(color_ostream &out, df::widget_unit_list } static void sort_set_work_animal_assignment_filter_fn(color_ostream &out, df::widget_unit_list *unitlist) { - if (unitlist && our_filter_idx(unitlist) == -1) { + if (unitlist && our_filter_idx(do_work_animal_assignment_filter, unitlist) == -1) { DEBUG(log).print("adding work animal assignment filter function\n"); auto filter_vec = reinterpret_cast(&unitlist->filter_func); filter_vec->emplace_back(do_work_animal_assignment_filter); diff --git a/plugins/spectate.cpp b/plugins/spectate.cpp index ae287f6000..072ad813c7 100644 --- a/plugins/spectate.cpp +++ b/plugins/spectate.cpp @@ -178,12 +178,12 @@ static class UnitHistory { void add_to_history(color_ostream &out, int32_t unit_id) { if (offset > 0) { - DEBUG(cycle,out).print("trimming history forward of offset %zd\n", offset); + DEBUG(cycle,out).print("trimming history forward of offset {}\n", offset); history.resize(history.size() - offset); offset = 0; } if (history.size() && history.back() == unit_id) { - DEBUG(cycle,out).print("unit %d is already current unit; not adding to history\n", unit_id); + DEBUG(cycle,out).print("unit {} is already current unit; not adding to history\n", unit_id); } else { history.push_back(unit_id); if (history.size() > MAX_HISTORY) { @@ -191,19 +191,19 @@ static class UnitHistory { history.pop_front(); } } - DEBUG(cycle,out).print("history now has %zd entries\n", history.size()); + DEBUG(cycle,out).print("history now has {} entries\n", history.size()); } void add_and_follow(color_ostream &out, df::unit *unit) { // if we're currently following a unit, add it to the history if it's not already there if (plotinfo->follow_unit > -1 && plotinfo->follow_unit != get_cur_unit_id()) { - DEBUG(cycle,out).print("currently following unit %d that is not in history; adding\n", plotinfo->follow_unit); + DEBUG(cycle,out).print("currently following unit {} that is not in history; adding\n", plotinfo->follow_unit); add_to_history(out, plotinfo->follow_unit); } int32_t id = unit->id; add_to_history(out, id); - DEBUG(cycle,out).print("now following unit %d: %s\n", id, DF2CONSOLE(Units::getReadableName(unit)).c_str()); + DEBUG(cycle,out).print("now following unit {}: {}\n", id, DF2CONSOLE(Units::getReadableName(unit))); Gui::revealInDwarfmodeMap(Units::getPosition(unit), false, World::ReadPauseState()); plotinfo->follow_item = -1; plotinfo->follow_unit = id; @@ -216,7 +216,7 @@ static class UnitHistory { } ++offset; int unit_id = get_cur_unit_id(); - DEBUG(cycle,out).print("scanning back to unit %d at offset %zd\n", unit_id, offset); + DEBUG(cycle,out).print("scanning back to unit {} at offset {}\n", unit_id, offset); if (auto unit = df::unit::find(unit_id)) Gui::revealInDwarfmodeMap(Units::getPosition(unit), false, World::ReadPauseState()); plotinfo->follow_item = -1; @@ -232,7 +232,7 @@ static class UnitHistory { --offset; int unit_id = get_cur_unit_id(); - DEBUG(cycle,out).print("scanning forward to unit %d at offset %zd\n", unit_id, offset); + DEBUG(cycle,out).print("scanning forward to unit {} at offset {}\n", unit_id, offset); if (auto unit = df::unit::find(unit_id)) Gui::revealInDwarfmodeMap(Units::getPosition(unit), false, World::ReadPauseState()); plotinfo->follow_item = -1; @@ -281,7 +281,7 @@ static class RecentUnits { static void on_new_active_unit(color_ostream& out, void* data) { int32_t unit_id = reinterpret_cast(data); - DEBUG(event,out).print("unit %d has arrived on map\n", unit_id); + DEBUG(event,out).print("unit {} has arrived on map\n", unit_id); recent_units.add(unit_id); } @@ -294,7 +294,7 @@ static command_result do_command(color_ostream &out, vector ¶meters) static void follow_a_dwarf(color_ostream &out); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(control,out).print("initializing %s\n", plugin_name); + DEBUG(control,out).print("initializing {}\n", plugin_name); commands.push_back(PluginCommand( plugin_name, @@ -320,19 +320,19 @@ static void set_next_cycle_unpaused_ms(color_ostream &out, bool has_active_comba delay_ms = distribution(rng); delay_ms = std::min(config.follow_ms, std::max(1, delay_ms)); } - DEBUG(cycle,out).print("next cycle in %d ms\n", delay_ms); + DEBUG(cycle,out).print("next cycle in {} ms\n", delay_ms); next_cycle_unpaused_ms = Core::getInstance().getUnpausedMs() + delay_ms; } DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } if (enable != is_enabled) { is_enabled = enable; - DEBUG(control,out).print("%s from the API; persisting\n", + DEBUG(control,out).print("{} from the API; persisting\n", is_enabled ? "enabled" : "disabled"); if (enable) { config.reset(); @@ -340,7 +340,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { WARN(control,out).print("Failed to refresh config\n"); } if (is_squads_open()) { - out.printerr("Cannot enable %s while the squads screen is open.\n", plugin_name); + out.printerr("Cannot enable {} while the squads screen is open.\n", plugin_name); Lua::CallLuaModuleFunction(out, "plugins.spectate", "show_squads_warning"); is_enabled = false; return CR_FAILURE; @@ -358,7 +358,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { // don't reset the unit history since we may want to re-enable } } else { - DEBUG(control,out).print("%s from the API, but already %s; no action\n", + DEBUG(control,out).print("{} from the API, but already {}; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); } @@ -366,7 +366,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { } DFhackCExport command_result plugin_shutdown (color_ostream &out) { - DEBUG(control,out).print("shutting down %s\n", plugin_name); + DEBUG(control,out).print("shutting down {}\n", plugin_name); on_disable(out); return CR_OK; } @@ -378,7 +378,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan break; case SC_WORLD_UNLOADED: if (is_enabled) { - DEBUG(control,out).print("world unloaded; disabling %s\n", + DEBUG(control,out).print("world unloaded; disabling {}\n", plugin_name); is_enabled = false; on_disable(out, true); @@ -456,7 +456,7 @@ static void get_dwarf_buckets(color_ostream &out, continue; if (is_in_combat(unit)) { - TRACE(cycle, out).print("unit %d is in combat: %s\n", unit->id, DF2CONSOLE(Units::getReadableName(unit)).c_str()); + TRACE(cycle, out).print("unit {} is in combat: {}\n", unit->id, DF2CONSOLE(Units::getReadableName(unit))); if (Units::isCitizen(unit, true) || Units::isResident(unit, true)) citizen_combat_units.push_back(unit); else @@ -498,17 +498,17 @@ static void add_bucket(const vector &bucket, vector &units } #define DUMP_BUCKET(name) \ - DEBUG(cycle,out).print("bucket: " #name ", size: %zd\n", name.size()); \ + DEBUG(cycle,out).print("bucket: " #name ", size: {}\n", name.size()); \ if (debug_cycle.isEnabled(DebugCategory::LTRACE)) { \ for (auto u : name) { \ - DEBUG(cycle,out).print(" unit %d: %s\n", u->id, DF2CONSOLE(Units::getReadableName(u)).c_str()); \ + DEBUG(cycle,out).print(" unit {}: {}\n", u->id, DF2CONSOLE(Units::getReadableName(u))); \ } \ } #define DUMP_FLOAT_VECTOR(name) \ DEBUG(cycle,out).print(#name ":\n"); \ for (float f : name) { \ - DEBUG(cycle,out).print(" %d\n", (int)f); \ + DEBUG(cycle,out).print(" {}\n", (int)f); \ } static void follow_a_dwarf(color_ostream &out) { @@ -551,7 +551,7 @@ static void follow_a_dwarf(color_ostream &out) { DUMP_BUCKET(other_units); DUMP_FLOAT_VECTOR(intervals); DUMP_FLOAT_VECTOR(weights); - DEBUG(cycle,out).print("selected unit idx %d\n", unit_idx); + DEBUG(cycle,out).print("selected unit idx {}\n", unit_idx); } unit_history.add_and_follow(out, unit); @@ -561,7 +561,7 @@ static void follow_a_dwarf(color_ostream &out) { // Lua API static void spectate_setSetting(color_ostream &out, string name, int val) { - DEBUG(control,out).print("entering spectate_setSetting %s = %d\n", name.c_str(), val); + DEBUG(control,out).print("entering spectate_setSetting {} = {}\n", name, val); if (name == "auto-unpause") { if (val && !config.auto_unpause) { @@ -593,7 +593,7 @@ static void spectate_setSetting(color_ostream &out, string name, int val) { } config.follow_ms = val * 1000; } else { - WARN(control,out).print("Unknown setting: %s\n", name.c_str()); + WARN(control,out).print("Unknown setting: {}\n", name); } } @@ -610,9 +610,9 @@ static void spectate_followNext(color_ostream &out) { }; static void spectate_addToHistory(color_ostream &out, int32_t unit_id) { - DEBUG(control,out).print("entering spectate_addToHistory; unit_id=%d\n", unit_id); + DEBUG(control,out).print("entering spectate_addToHistory; unit_id={}\n", unit_id); if (!df::unit::find(unit_id)) { - WARN(control,out).print("unit with id %d not found; not adding to history\n", unit_id); + WARN(control,out).print("unit with id {} not found; not adding to history\n", unit_id); return; } unit_history.add_to_history(out, unit_id); diff --git a/plugins/steam-engine.cpp b/plugins/steam-engine.cpp index befd53f10a..06b14aa6c0 100644 --- a/plugins/steam-engine.cpp +++ b/plugins/steam-engine.cpp @@ -870,7 +870,7 @@ static bool find_engines(color_ostream &out) if (!ws.gear_tiles.empty()) engines.push_back(ws); else - out.printerr("%s has no gear tiles - ignoring.\n", wslist[i]->code.c_str()); + out.printerr("{} has no gear tiles - ignoring.\n", wslist[i]->code); } return !engines.empty(); diff --git a/plugins/stockflow.cpp b/plugins/stockflow.cpp index 1671d08991..524bd16896 100644 --- a/plugins/stockflow.cpp +++ b/plugins/stockflow.cpp @@ -284,7 +284,7 @@ static bool apply_hooks(color_ostream &out, bool enabling) { } if (!INTERPOSE_HOOK(stockflow_hook, feed).apply(enabling) || !INTERPOSE_HOOK(stockflow_hook, render).apply(enabling)) { - out.printerr("Could not %s stockflow hooks!\n", enabling? "insert": "remove"); + out.printerr("Could not {} stockflow hooks!\n", enabling? "insert": "remove"); return false; } @@ -336,7 +336,7 @@ static command_result stockflow_cmd(color_ostream &out, vector & parame } } - out.print("Stockflow is %s %s%s.\n", (desired == enabled)? "currently": "now", desired? "enabled": "disabled", fast? ", in fast mode": ""); + out.print("Stockflow is {} {}{}.\n", (desired == enabled)? "currently": "now", desired? "enabled": "disabled", fast? ", in fast mode": ""); enabled = desired; return CR_OK; } diff --git a/plugins/stockpiles/OrganicMatLookup.cpp b/plugins/stockpiles/OrganicMatLookup.cpp index 879353f3b0..1392e8c12c 100644 --- a/plugins/stockpiles/OrganicMatLookup.cpp +++ b/plugins/stockpiles/OrganicMatLookup.cpp @@ -22,7 +22,7 @@ DBG_EXTERN(stockpiles, log); */ void OrganicMatLookup::food_mat_by_idx(color_ostream& out, organic_mat_category::organic_mat_category mat_category, std::vector::size_type food_idx, FoodMat& food_mat) { - DEBUG(log, out).print("food_lookup: food_idx(%zd)\n", food_idx); + DEBUG(log, out).print("food_lookup: food_idx({})\n", food_idx); auto& raws = world->raws; df::special_mat_table table = raws.mat_table; int32_t main_idx = table.organic_indexes[mat_category][food_idx]; @@ -32,11 +32,11 @@ void OrganicMatLookup::food_mat_by_idx(color_ostream& out, organic_mat_category: mat_category == organic_mat_category::Eggs) { food_mat.creature = raws.creatures.all[type]; food_mat.caste = food_mat.creature->caste[main_idx]; - DEBUG(log, out).print("special creature type(%d) caste(%d)\n", type, main_idx); + DEBUG(log, out).print("special creature type({}) caste({})\n", type, main_idx); } else { food_mat.material.decode(type, main_idx); - DEBUG(log, out).print("type(%d) index(%d) token(%s)\n", type, main_idx, food_mat.material.getToken().c_str()); + DEBUG(log, out).print("type({}) index({}) token({})\n", type, main_idx, food_mat.material.getToken()); } } std::string OrganicMatLookup::food_token_by_idx(color_ostream& out, const FoodMat& food_mat) { @@ -80,22 +80,22 @@ int16_t OrganicMatLookup::food_idx_by_token(color_ostream& out, organic_mat_cate std::vector tokens; split_string(&tokens, token, ":"); if (tokens.size() != 2) { - WARN(log, out).print("creature invalid CREATURE:CASTE token: %s\n", token.c_str()); + WARN(log, out).print("creature invalid CREATURE:CASTE token: {}\n", token); return -1; } int16_t creature_idx = find_creature(tokens[0]); if (creature_idx < 0) { - WARN(log, out).print("creature invalid token %s\n", tokens[0].c_str()); + WARN(log, out).print("creature invalid token {}\n", tokens[0]); return -1; } int16_t food_idx = linear_index(table.organic_types[mat_category], creature_idx); if (tokens[1] == "MALE") food_idx += 1; if (table.organic_types[mat_category][food_idx] == creature_idx) { - DEBUG(log, out).print("creature %s caste %s creature_idx(%d) food_idx(%d)\n", token.c_str(), tokens[1].c_str(), creature_idx, food_idx); + DEBUG(log, out).print("creature {} caste {} creature_idx({}) food_idx({})\n", token, tokens[1], creature_idx, food_idx); return food_idx; } - WARN(log, out).print("creature caste not found: %s caste %s creature_idx(%d) food_idx(%d)\n", token.c_str(), tokens[1].c_str(), creature_idx, food_idx); + WARN(log, out).print("creature caste not found: {} caste {} creature_idx({}) food_idx({})\n", token, tokens[1], creature_idx, food_idx); return -1; } @@ -106,12 +106,12 @@ int16_t OrganicMatLookup::food_idx_by_token(color_ostream& out, organic_mat_cate int32_t index = mat_info.index; auto it = food_index[mat_category].find(std::make_pair(type, index)); if (it != food_index[mat_category].end()) { - DEBUG(log, out).print("matinfo: %s type(%d) idx(%d) food_idx(%zd)\n", token.c_str(), mat_info.type, mat_info.index, it->second); + DEBUG(log, out).print("matinfo: {} type({}) idx({}) food_idx({})\n", token, mat_info.type, mat_info.index, it->second); return it->second; } - WARN(log, out).print("matinfo: %s type(%d) idx(%d) food_idx not found :(\n", token.c_str(), mat_info.type, mat_info.index); + WARN(log, out).print("matinfo: {} type({}) idx({}) food_idx not found :(\n", token, mat_info.type, mat_info.index); return -1; } diff --git a/plugins/stockpiles/StockpileSerializer.cpp b/plugins/stockpiles/StockpileSerializer.cpp index efac3e8bad..6e90cb797e 100644 --- a/plugins/stockpiles/StockpileSerializer.cpp +++ b/plugins/stockpiles/StockpileSerializer.cpp @@ -12,6 +12,7 @@ #include "df/building_stockpilest.h" #include "df/creature_raw.h" #include "df/caste_raw.h" +#include "df/descriptor_color.h" #include "df/inorganic_raw.h" #include "df/item_quality.h" #include @@ -179,8 +180,8 @@ bool StockpileSettingsSerializer::serialize_to_ostream(color_ostream& out, std:: bool StockpileSettingsSerializer::serialize_to_file(color_ostream& out, const string& file, uint32_t includedElements) { std::fstream output(file, std::ios::out | std::ios::binary | std::ios::trunc); if (output.fail()) { - WARN(log, out).print("ERROR: failed to open file for writing: '%s'\n", - file.c_str()); + WARN(log, out).print("ERROR: failed to open file for writing: '{}'\n", + file); return false; } return serialize_to_ostream(out, &output, includedElements); @@ -201,8 +202,8 @@ bool StockpileSettingsSerializer::parse_from_istream(color_ostream &out, std::is bool StockpileSettingsSerializer::unserialize_from_file(color_ostream &out, const string& file, DeserializeMode mode, const vector& filters) { std::fstream input(file, std::ios::in | std::ios::binary); if (input.fail()) { - WARN(log, out).print("failed to open file for reading: '%s'\n", - file.c_str()); + WARN(log, out).print("failed to open file for reading: '{}'\n", + file); return false; } return parse_from_istream(out, &input, mode, filters); @@ -223,7 +224,7 @@ static typename df::enum_traits::base_type token_to_enum_val(const string& to static bool matches_filter(color_ostream& out, const vector& filters, const string& name) { for (auto & filter : filters) { - DEBUG(log, out).print("searching for '%s' in '%s'\n", filter.c_str(), name.c_str()); + DEBUG(log, out).print("searching for '{}' in '{}'\n", filter, name); if (std::search(name.begin(), name.end(), filter.begin(), filter.end(), [](unsigned char ch1, unsigned char ch2) { return std::toupper(ch1) == std::toupper(ch2); } ) != name.end()) @@ -234,7 +235,7 @@ static bool matches_filter(color_ostream& out, const vector& filters, co static void set_flag(color_ostream& out, const char* name, const vector& filters, bool all, char val, bool enabled, bool& elem) { if ((all || enabled) && matches_filter(out, filters, name)) { - DEBUG(log, out).print("setting %s to %d\n", name, val); + DEBUG(log, out).print("setting {} to {}\n", name, val); elem = val; } } @@ -242,7 +243,7 @@ static void set_flag(color_ostream& out, const char* name, const vector& static void set_filter_elem(color_ostream& out, const char* subcat, const vector& filters, char val, const string& name, const string& id, char& elem) { if (matches_filter(out, filters, subcat + ((*subcat ? "/" : "") + name))) { - DEBUG(log, out).print("setting %s (%s) to %d\n", name.c_str(), id.c_str(), val); + DEBUG(log, out).print("setting {} ({}) to {}\n", name, id, val); elem = val; } } @@ -251,7 +252,7 @@ template static void set_filter_elem(color_ostream& out, const char* subcat, const vector& filters, T_val val, const string& name, T_id id, T_val& elem) { if (matches_filter(out, filters, subcat + ((*subcat ? "/" : "") + name))) { - DEBUG(log, out).print("setting %s (%d) to %d\n", name.c_str(), (int32_t)id, val); + DEBUG(log, out).print("setting {} ({}) to {}\n", name, id, val); elem = val; } } @@ -286,7 +287,7 @@ static bool serialize_list_itemdef(color_ostream& out, FuncWriteExport add_value ItemTypeInfo ii; if (!ii.decode(type, i)) continue; - DEBUG(log, out).print("adding itemdef type %s\n", ii.getToken().c_str()); + DEBUG(log, out).print("adding itemdef type {}\n", ii.getToken()); add_value(ii.getToken()); } return all; @@ -311,7 +312,7 @@ static void unserialize_list_itemdef(color_ostream& out, const char* subcat, boo if (!ii.find(id)) continue; if (ii.subtype < 0 || size_t(ii.subtype) >= pile_list.size()) { - WARN(log, out).print("item type index invalid: %d\n", ii.subtype); + WARN(log, out).print("item type index invalid: {}\n", ii.subtype); continue; } set_filter_elem(out, subcat, filters, val, id, ii.subtype, pile_list.at(ii.subtype)); @@ -319,7 +320,7 @@ static void unserialize_list_itemdef(color_ostream& out, const char* subcat, boo } static bool serialize_list_quality(color_ostream& out, FuncWriteExport add_value, - const bool(&quality_list)[7]) { + const std::array &quality_list) { using df::enums::item_quality::item_quality; using quality_traits = df::enum_traits; @@ -331,17 +332,17 @@ static bool serialize_list_quality(color_ostream& out, FuncWriteExport add_value } const string f_type(quality_traits::key_table[i]); add_value(f_type); - DEBUG(log, out).print("adding quality %s\n", f_type.c_str()); + DEBUG(log, out).print("adding quality {}\n", f_type); } return all; } -static void quality_clear(bool(&pile_list)[7]) { - std::fill(pile_list, pile_list + 7, false); +static void quality_clear(std::array &pile_list) { + pile_list.fill(false); } static void unserialize_list_quality(color_ostream& out, const char* subcat, bool all, bool val, const vector& filters, - FuncReadImport read_value, int32_t list_size, bool(&pile_list)[7]) { + FuncReadImport read_value, int32_t list_size, std::array &pile_list) { if (all) { for (auto idx = 0; idx < 7; ++idx) { string id = ENUM_KEY_STR(item_quality, (df::item_quality)idx); @@ -355,7 +356,7 @@ static void unserialize_list_quality(color_ostream& out, const char* subcat, boo const string quality = read_value(i); df::enum_traits::base_type idx = token_to_enum_val(quality); if (idx < 0) { - WARN(log, out).print("invalid quality token: %s\n", quality.c_str()); + WARN(log, out).print("invalid quality token: {}\n", quality); continue; } set_filter_elem(out, subcat, filters, val, quality, idx, pile_list[idx]); @@ -391,11 +392,11 @@ static bool serialize_list_other_mats(color_ostream& out, } const string token = other_mats_index(other_mats, i); if (token.empty()) { - WARN(log, out).print("invalid other material with index %zd\n", i); + WARN(log, out).print("invalid other material with index {}\n", i); continue; } add_value(token); - DEBUG(log, out).print("other mats %zd is %s\n", i, token.c_str()); + DEBUG(log, out).print("other mats {} is {}\n", i, token); } return all; } @@ -415,11 +416,11 @@ static void unserialize_list_other_mats(color_ostream& out, const char* subcat, const string token = read_value(i); size_t idx = other_mats_token(other_mats, token); if (idx < 0) { - WARN(log, out).print("invalid other mat with token %s\n", token.c_str()); + WARN(log, out).print("invalid other mat with token {}\n", token); continue; } if (idx >= num_elems) { - WARN(log, out).print("other_mats index too large! idx[%zd] max_size[%zd]\n", idx, num_elems); + WARN(log, out).print("other_mats index too large! idx[{}] max_size[{}]\n", idx, num_elems); continue; } set_filter_elem(out, subcat, filters, val, token, idx, pile_list.at(idx)); @@ -446,7 +447,7 @@ static bool serialize_list_organic_mat(color_ostream& out, FuncWriteExport add_v DEBUG(log, out).print("food mat invalid :(\n"); continue; } - DEBUG(log, out).print("organic_material %zd is %s\n", i, token.c_str()); + DEBUG(log, out).print("organic_material {} is {}\n", i, token); add_value(token); } return all; @@ -488,7 +489,7 @@ static void unserialize_list_organic_mat(color_ostream& out, const char* subcat, const string token = read_value(i); int16_t idx = OrganicMatLookup::food_idx_by_token(out, cat, token); if (idx < 0 || size_t(idx) >= num_elems) { - WARN(log, out).print("organic mat index too large! idx[%d] max_size[%zd]\n", idx, num_elems); + WARN(log, out).print("organic mat index too large! idx[{}] max_size[{}]\n", idx, num_elems); continue; } OrganicMatLookup::FoodMat food_mat; @@ -497,6 +498,57 @@ static void unserialize_list_organic_mat(color_ostream& out, const char* subcat, } } +static bool serialize_list_color(color_ostream& out, FuncWriteExport add_value, const vector* colors) { + bool all = world->raws.descriptors.colors.size() == colors->size(); + if (!colors) { + DEBUG(log, out).print("serialize_list_color: list null\n"); + return all; + } + for (size_t i = 0; i < colors->size(); ++i) { + if (!colors->at(i)) { + all = false; + continue; + } + add_value(world->raws.descriptors.colors[i]->id); + } + return all; +} + +// Find the index of a named color, returning SIZE_MAX on unknown. +static size_t find_color_by_token(const string& token) { + auto& colors = world->raws.descriptors.colors; + size_t num_colors = colors.size(); + for (size_t idx = 0; idx < num_colors; ++ idx) { + if (colors[idx]->id == token) { + return idx; + } + } + return SIZE_MAX; +} + +static void unserialize_list_color(color_ostream& out, const char* subcat, bool all, char val, const vector& filters, + FuncReadImport read_value, int32_t list_size, vector& pile_list) { + size_t num_elems = world->raws.descriptors.colors.size(); + pile_list.resize(num_elems, '\0'); + if (all) { + for (size_t idx = 0; idx < num_elems; ++idx) { + set_filter_elem(out, subcat, filters, val, world->raws.descriptors.colors[idx]->id, idx, pile_list[idx]); + } + return; + } + + for (int32_t idx = 0; idx < list_size; ++idx) { + const string& value = read_value(idx); + + size_t color = find_color_by_token(value); + if (color == SIZE_MAX) { + WARN(log, out).print("unknown color {}\n", value); + continue; + } + set_filter_elem(out, subcat, filters, val, value, color, pile_list[color]); + } +} + static bool serialize_list_item_type(color_ostream& out, FuncItemAllowed is_allowed, FuncWriteExport add_value, const vector& list) { using df::enums::item_type::item_type; @@ -504,7 +556,7 @@ static bool serialize_list_item_type(color_ostream& out, FuncItemAllowed is_allo bool all = true; size_t num_item_types = list.size(); - DEBUG(log, out).print("item_type size = %zd size limit = %d typecasted: %zd\n", + DEBUG(log, out).print("item_type size = {} size limit = {} typecasted: {}\n", num_item_types, type_traits::last_item_value, (size_t)type_traits::last_item_value); for (size_t i = 0; i <= (size_t)type_traits::last_item_value; ++i) { @@ -517,7 +569,7 @@ static bool serialize_list_item_type(color_ostream& out, FuncItemAllowed is_allo if (!is_allowed(type)) continue; add_value(r_type); - DEBUG(log, out).print("item_type key_table[%zd] type[%d] is %s\n", i + 1, (int16_t)type, r_type.c_str()); + DEBUG(log, out).print("item_type key_table[{}] type[{}] is {}\n", i + 1, (int16_t)type, r_type); } return all; } @@ -547,7 +599,7 @@ static void unserialize_list_item_type(color_ostream& out, const char* subcat, b if (!is_allowed((item_type)idx)) continue; if (idx < 0 || size_t(idx) >= num_elems) { - WARN(log, out).print("error item_type index too large! idx[%d] max_size[%zd]\n", idx, num_elems); + WARN(log, out).print("error item_type index too large! idx[{}] max_size[{}]\n", idx, num_elems); continue; } set_filter_elem(out, subcat, filters, val, token, idx, pile_list.at(idx)); @@ -566,7 +618,7 @@ static bool serialize_list_material(color_ostream& out, FuncMaterialAllowed is_a mi.decode(0, i); if (!is_allowed(mi)) continue; - DEBUG(log, out).print("adding material %s\n", mi.getToken().c_str()); + DEBUG(log, out).print("adding material {}\n", mi.getToken()); add_value(mi.getToken()); } return all; @@ -600,7 +652,7 @@ static void unserialize_list_material(color_ostream& out, const char* subcat, bo if (!mi.find(id) || !is_allowed(mi)) continue; if (mi.index < 0 || size_t(mi.index) >= pile_list.size()) { - WARN(log, out).print("material type index invalid: %d\n", mi.index); + WARN(log, out).print("material type index invalid: {}\n", mi.index); continue; } set_filter_elem(out, subcat, filters, val, id, mi.index, pile_list.at(mi.index)); @@ -619,7 +671,7 @@ static bool serialize_list_creature(color_ostream& out, FuncWriteExport add_valu if (r->flags.is_set(creature_raw_flags::GENERATED) || r->creature_id == "EQUIPMENT_WAGON") continue; - DEBUG(log, out).print("adding creature %s\n", r->creature_id.c_str()); + DEBUG(log, out).print("adding creature {}\n", r->creature_id); add_value(r->creature_id); } return all; @@ -649,7 +701,7 @@ static void unserialize_list_creature(color_ostream& out, const char* subcat, bo string id = read_value(i); int idx = find_creature(id); if (idx < 0 || size_t(idx) >= num_elems) { - WARN(log, out).print("animal index invalid: %d\n", idx); + WARN(log, out).print("animal index invalid: {}\n", idx); continue; } auto r = find_creature(idx); @@ -669,7 +721,7 @@ static void write_cat(color_ostream& out, const char* name, bool include_types, T_cat_set* cat_set = mutable_cat_fn(); if (!include_types) { - DEBUG(log, out).print("including all for %s since only category is being recorded\n", name); + DEBUG(log, out).print("including all for {} since only category is being recorded\n", name); cat_set->set_all(true); return; } @@ -677,7 +729,7 @@ static void write_cat(color_ostream& out, const char* name, bool include_types, if (write_cat_fn(out, cat_set)) { // all fields were set. clear them and use the "all" flag instead so "all" can be applied // to other worlds with other generated types - DEBUG(log, out).print("including all for %s since all fields were enabled\n", name); + DEBUG(log, out).print("including all for {} since all fields were enabled\n", name); cat_set->Clear(); cat_set->set_all(true); } @@ -690,7 +742,7 @@ void StockpileSettingsSerializer::write(color_ostream& out, uint32_t includedEle if (!(includedElements & INCLUDED_ELEMENTS_CATEGORIES)) return; - DEBUG(log, out).print("GROUP SET %s\n", + DEBUG(log, out).print("GROUP SET {}\n", bitfield_to_string(mSettings->flags).c_str()); bool include_types = 0 != (includedElements & INCLUDED_ELEMENTS_TYPES); @@ -841,7 +893,7 @@ static void read_elem(color_ostream& out, const char* name, DeserializeMode mode bool is_set = elem_fn() != 0; if (mode == DESERIALIZE_MODE_SET || is_set) { T_elem val = (mode == DESERIALIZE_MODE_DISABLE) ? 0 : elem_fn(); - DEBUG(log, out).print("setting %s to %d\n", name, val); + DEBUG(log, out).print("setting {} to {}\n", name, val); setting = val; } } @@ -855,7 +907,7 @@ static void read_category(color_ostream& out, const char* name, DeserializeMode std::function clear_fn, std::function set_fn) { if (mode == DESERIALIZE_MODE_SET) { - DEBUG(log, out).print("clearing %s\n", name); + DEBUG(log, out).print("clearing {}\n", name); cat_flags &= ~cat_mask; clear_fn(); } @@ -871,7 +923,7 @@ static void read_category(color_ostream& out, const char* name, DeserializeMode bool all = cat_fn().all(); char val = (mode == DESERIALIZE_MODE_DISABLE) ? (char)0 : (char)1; - DEBUG(log, out).print("setting %s %s elements to %d\n", + DEBUG(log, out).print("setting {} {} elements to {}\n", all ? "all" : "marked", name, val); set_fn(all, val); } @@ -918,7 +970,7 @@ void StockpileSerializer::read_general(color_ostream& out, DeserializeMode mode) if (!mBuffer.has_use_links_only()) return; bool use_links_only = mBuffer.use_links_only(); - DEBUG(log, out).print("setting use_links_only to %d\n", use_links_only); + DEBUG(log, out).print("setting use_links_only to {}\n", use_links_only); mPile->stockpile_flag.bits.use_links_only = use_links_only; } @@ -939,7 +991,7 @@ bool StockpileSettingsSerializer::write_ammo(color_ostream& out, StockpileSettin mSettings->ammo.mats) && all; if (mSettings->ammo.other_mats.size() > 2) { - WARN(log, out).print("ammo other materials > 2: %zd\n", + WARN(log, out).print("ammo other materials > 2: {}\n", mSettings->ammo.other_mats.size()); } @@ -952,7 +1004,7 @@ bool StockpileSettingsSerializer::write_ammo(color_ostream& out, StockpileSettin } const string token = i == 0 ? "WOOD" : "BONE"; ammo->add_other_mats(token); - DEBUG(log, out).print("other mats %zd is %s\n", i, token.c_str()); + DEBUG(log, out).print("other mats {} is {}\n", i, token); } all = serialize_list_quality(out, @@ -1059,10 +1111,12 @@ static bool armor_mat_is_allowed(const MaterialInfo& mi) { bool StockpileSettingsSerializer::write_armor(color_ostream& out, StockpileSettings::ArmorSet* armor) { auto & parmor = mSettings->armor; - bool all = parmor.unusable && parmor.usable; + bool all = parmor.unusable && parmor.usable && parmor.dyed && parmor.undyed; armor->set_unusable(parmor.unusable); armor->set_usable(parmor.usable); + armor->set_dyed(parmor.dyed); + armor->set_undyed(parmor.undyed); // armor type all = serialize_list_itemdef(out, @@ -1125,6 +1179,9 @@ bool StockpileSettingsSerializer::write_armor(color_ostream& out, StockpileSetti all = serialize_list_quality(out, [&](const string& token) { armor->add_quality_total(token); }, parmor.quality_total) && all; + all = serialize_list_color(out, [&](const string& token) { armor->add_color(token); }, + &parmor.color) && all; + return all; } @@ -1148,6 +1205,9 @@ void StockpileSettingsSerializer::read_armor(color_ostream& out, DeserializeMode parmor.mats.clear(); quality_clear(parmor.quality_core); quality_clear(parmor.quality_total); + parmor.dyed = false; + parmor.undyed = false; + parmor.color.clear(); }, [&](bool all, char val) { auto & barmor = mBuffer.armor(); @@ -1195,6 +1255,13 @@ void StockpileSettingsSerializer::read_armor(color_ostream& out, DeserializeMode unserialize_list_quality(out, "total", all, val, filters, [&](const size_t& idx) -> const string& { return barmor.quality_total(idx); }, barmor.quality_total_size(), parmor.quality_total); + + unserialize_list_color(out, "color", all, val, filters, + [&](const size_t& idx) -> const string& { return barmor.color(idx); }, + barmor.color_size(), parmor.color); + + set_flag(out, "dyed", filters, all, val, barmor.dyed(), parmor.dyed); + set_flag(out, "undyed", filters, all, val, barmor.undyed(), parmor.undyed); }); } @@ -1266,7 +1333,9 @@ void StockpileSettingsSerializer::read_bars_blocks(color_ostream& out, Deseriali } bool StockpileSettingsSerializer::write_cloth(color_ostream& out, StockpileSettings::ClothSet* cloth) { - bool all = true; + bool all = mSettings->cloth.dyed && mSettings->cloth.undyed; + cloth->set_dyed(mSettings->cloth.dyed); + cloth->set_undyed(mSettings->cloth.undyed); all = serialize_list_organic_mat(out, [&](const string& token) { cloth->add_thread_silk(token); }, @@ -1300,6 +1369,10 @@ bool StockpileSettingsSerializer::write_cloth(color_ostream& out, StockpileSetti [&](const string& token) { cloth->add_cloth_metal(token); }, &mSettings->cloth.cloth_metal, organic_mat_category::MetalThread) && all; + all = serialize_list_color(out, + [&](const string& token) { cloth->add_color(token); }, + &mSettings->cloth.color) && all; + return all; } @@ -1319,6 +1392,9 @@ void StockpileSettingsSerializer::read_cloth(color_ostream& out, DeserializeMode pcloth.cloth_plant.clear(); pcloth.cloth_yarn.clear(); pcloth.cloth_metal.clear(); + pcloth.dyed = false; + pcloth.undyed = false; + pcloth.color.clear(); }, [&](bool all, char val) { auto & bcloth = mBuffer.cloth(); @@ -1354,6 +1430,13 @@ void StockpileSettingsSerializer::read_cloth(color_ostream& out, DeserializeMode unserialize_list_organic_mat(out, "cloth/metal", all, val, filters, [&](size_t idx) -> string { return bcloth.cloth_metal(idx); }, bcloth.cloth_metal_size(), pcloth.cloth_metal, organic_mat_category::MetalThread); + + unserialize_list_color(out, "cloth/color", all, val, filters, + [&](size_t idx) -> string { return bcloth.color(idx); }, + bcloth.color_size(), pcloth.color); + + set_flag(out, "dyed", filters, all, val, bcloth.dyed(), pcloth.dyed); + set_flag(out, "undyed", filters, all, val, bcloth.undyed(), pcloth.undyed); }); } @@ -1425,10 +1508,14 @@ static bool finished_goods_mat_is_allowed(const MaterialInfo& mi) { } bool StockpileSettingsSerializer::write_finished_goods(color_ostream& out, StockpileSettings::FinishedGoodsSet* finished_goods) { - bool all = serialize_list_item_type(out, + bool all = mSettings->finished_goods.dyed && mSettings->finished_goods.undyed; + finished_goods->set_dyed(mSettings->finished_goods.dyed); + finished_goods->set_undyed(mSettings->finished_goods.undyed); + + all = serialize_list_item_type(out, finished_goods_type_is_allowed, [&](const string& token) { finished_goods->add_type(token); }, - mSettings->finished_goods.type); + mSettings->finished_goods.type) && all; all = serialize_list_material(out, finished_goods_mat_is_allowed, @@ -1447,6 +1534,10 @@ bool StockpileSettingsSerializer::write_finished_goods(color_ostream& out, Stock [&](const string& token) { finished_goods->add_quality_total(token); }, mSettings->finished_goods.quality_total) && all; + all = serialize_list_color(out, + [&](const string& token) { finished_goods->add_color(token); }, + &mSettings->finished_goods.color) && all; + return all; } @@ -1463,6 +1554,9 @@ void StockpileSettingsSerializer::read_finished_goods(color_ostream& out, Deseri pfinished_goods.mats.clear(); quality_clear(pfinished_goods.quality_core); quality_clear(pfinished_goods.quality_total); + pfinished_goods.dyed = false; + pfinished_goods.undyed = false; + pfinished_goods.color.clear(); }, [&](bool all, char val) { auto & bfinished_goods = mBuffer.finished_goods(); @@ -1486,6 +1580,13 @@ void StockpileSettingsSerializer::read_finished_goods(color_ostream& out, Deseri unserialize_list_quality(out, "total", all, val, filters, [&](const size_t& idx) -> const string& { return bfinished_goods.quality_total(idx); }, bfinished_goods.quality_total_size(), pfinished_goods.quality_total); + + unserialize_list_color(out, "color", all, val, filters, + [&](const size_t& idx) -> const string& { return bfinished_goods.color(idx); }, + bfinished_goods.color_size(), pfinished_goods.color); + + set_flag(out, "dyed", filters, all, val, bfinished_goods.dyed(), pfinished_goods.dyed); + set_flag(out, "undyed", filters, all, val, bfinished_goods.undyed(), pfinished_goods.undyed); }); } @@ -1742,7 +1843,7 @@ bool StockpileSettingsSerializer::write_furniture(color_ostream& out, StockpileS } string f_type{ENUM_KEY_STR(furniture_type, furniture_type(i))}; furniture->add_type(f_type); - DEBUG(log, out).print("furniture_type %zd is %s\n", i, f_type.c_str()); + DEBUG(log, out).print("furniture_type {} is {}\n", i, f_type); } all = serialize_list_material(out, @@ -1795,7 +1896,7 @@ void StockpileSettingsSerializer::read_furniture(color_ostream& out, Deserialize const string token = bfurniture.type(i); df::enum_traits::base_type idx = token_to_enum_val(token); if (idx < 0 || size_t(idx) >= pfurniture.type.size()) { - WARN(log, out).print("furniture type index invalid %s, idx=%d\n", token.c_str(), idx); + WARN(log, out).print("furniture type index invalid {}, idx={}\n", token, idx); continue; } set_filter_elem(out, "type", filters, val, token, idx, pfurniture.type.at(idx)); @@ -1855,7 +1956,7 @@ bool StockpileSettingsSerializer::write_gems(color_ostream& out, StockpileSettin mi.decode(i, -1); if (!gem_other_mat_is_allowed(mi)) continue; - DEBUG(log, out).print("gem rough_other mat %zd is %s\n", i, mi.getToken().c_str()); + DEBUG(log, out).print("gem rough_other mat {} is {}\n", i, mi.getToken()); gems->add_rough_other_mats(mi.getToken()); } @@ -1869,7 +1970,7 @@ bool StockpileSettingsSerializer::write_gems(color_ostream& out, StockpileSettin mi.decode(0, i); if (!gem_other_mat_is_allowed(mi)) continue; - DEBUG(log, out).print("gem cut_other mat %zd is %s\n", i, mi.getToken().c_str()); + DEBUG(log, out).print("gem cut_other mat {} is {}\n", i, mi.getToken()); gems->add_cut_other_mats(mi.getToken()); } @@ -1934,9 +2035,17 @@ void StockpileSettingsSerializer::read_gems(color_ostream& out, DeserializeMode } bool StockpileSettingsSerializer::write_leather(color_ostream& out, StockpileSettings::LeatherSet* leather) { - return serialize_list_organic_mat(out, + bool all = mSettings->leather.dyed && mSettings->leather.undyed; + leather->set_dyed(mSettings->leather.dyed); + leather->set_undyed(mSettings->leather.undyed); + + all = serialize_list_organic_mat(out, [&](const string& id) { leather->add_mats(id); }, - &mSettings->leather.mats, organic_mat_category::Leather); + &mSettings->leather.mats, organic_mat_category::Leather) && all; + all = serialize_list_color(out, + [&](const string& id) { leather->add_color(id); }, + &mSettings->leather.color) && all; + return all; } void StockpileSettingsSerializer::read_leather(color_ostream& out, DeserializeMode mode, const vector& filters) { @@ -1948,6 +2057,9 @@ void StockpileSettingsSerializer::read_leather(color_ostream& out, DeserializeMo mSettings->flags.mask_leather, [&]() { pleather.mats.clear(); + pleather.color.clear(); + pleather.dyed = false; + pleather.undyed = false; }, [&](bool all, char val) { auto & bleather = mBuffer.leather(); @@ -1955,6 +2067,12 @@ void StockpileSettingsSerializer::read_leather(color_ostream& out, DeserializeMo unserialize_list_organic_mat(out, "", all, val, filters, [&](size_t idx) -> string { return bleather.mats(idx); }, bleather.mats_size(), pleather.mats, organic_mat_category::Leather); + unserialize_list_color(out, "", all, val, filters, + [&](size_t idx) -> string { return bleather.color(idx); }, + bleather.color_size(), pleather.color); + + set_flag(out, "dyed", filters, all, val, bleather.dyed(), pleather.dyed); + set_flag(out, "undyed", filters, all, val, bleather.undyed(), pleather.undyed); }); } @@ -2266,7 +2384,7 @@ bool StockpileSettingsSerializer::write_wood(color_ostream& out, StockpileSettin if (!wood_mat_is_allowed(plant)) continue; wood->add_mats(plant->id); - DEBUG(log, out).print("plant %zd is %s\n", i, plant->id.c_str()); + DEBUG(log, out).print("plant {} is {}\n", i, plant->id); } return all; } @@ -2297,7 +2415,7 @@ void StockpileSettingsSerializer::read_wood(color_ostream& out, DeserializeMode const string token = bwood.mats(i); const size_t idx = find_plant(token); if (idx < 0 || (size_t)idx >= num_elems) { - WARN(log, out).print("wood mat index invalid %s idx=%zd\n", token.c_str(), idx); + WARN(log, out).print("wood mat index invalid {}, idx={}\n", token, idx); continue; } set_filter_elem(out, "", filters, val, token, idx, pwood.mats.at(idx)); diff --git a/plugins/stockpiles/proto/stockpiles.proto b/plugins/stockpiles/proto/stockpiles.proto index 681e7d9279..49a6d1c42e 100644 --- a/plugins/stockpiles/proto/stockpiles.proto +++ b/plugins/stockpiles/proto/stockpiles.proto @@ -100,10 +100,16 @@ message StockpileSettings { repeated string mats = 3; repeated string quality_core = 4; repeated string quality_total = 5; + optional bool dyed = 7; + optional bool undyed = 8; + repeated string color = 9; } message LeatherSet { optional bool all = 2; repeated string mats = 1; + optional bool dyed = 3; + optional bool undyed = 4; + repeated string color = 5; } message ClothSet { optional bool all = 9; @@ -115,6 +121,9 @@ message StockpileSettings { repeated string cloth_plant = 6; repeated string cloth_yarn = 7; repeated string cloth_metal = 8; + optional bool dyed = 10; + optional bool undyed = 11; + repeated string color = 12; } message WoodSet { optional bool all = 2; @@ -145,6 +154,9 @@ message StockpileSettings { repeated string quality_total = 10; optional bool usable = 11; optional bool unusable = 12; + optional bool dyed = 14; + optional bool undyed = 15; + repeated string color = 16; } message CorpsesSet { optional bool all = 1; diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index 33ce855b42..7165449cca 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -29,7 +29,7 @@ DBG_DECLARE(stockpiles, log, DebugCategory::LINFO); static command_result do_command(color_ostream& out, vector& parameters); DFhackCExport command_result plugin_init(color_ostream &out, vector &commands) { - DEBUG(log, out).print("initializing %s\n", plugin_name); + DEBUG(log, out).print("initializing {}\n", plugin_name); commands.push_back(PluginCommand( plugin_name, @@ -62,7 +62,7 @@ static df::building_stockpilest* get_stockpile(int id) { static bool stockpiles_export(color_ostream& out, string fname, int id, uint32_t includedElements) { df::building_stockpilest* sp = get_stockpile(id); if (!sp) { - out.printerr("Specified building isn't a stockpile: %d.\n", id); + out.printerr("Specified building isn't a stockpile: {}\n", id); return false; } @@ -72,12 +72,12 @@ static bool stockpiles_export(color_ostream& out, string fname, int id, uint32_t try { StockpileSerializer cereal(sp); if (!cereal.serialize_to_file(out, fname, includedElements)) { - out.printerr("could not save to '%s'\n", fname.c_str()); + out.printerr("could not save to '{}'\n", fname); return false; } } catch (std::exception& e) { - out.printerr("serialization failed: protobuf exception: %s\n", e.what()); + out.printerr("serialization failed: protobuf exception: {}\n", e.what()); return false; } @@ -87,7 +87,7 @@ static bool stockpiles_export(color_ostream& out, string fname, int id, uint32_t static bool stockpiles_import(color_ostream& out, string fname, int id, string mode_str, string filter) { df::building_stockpilest* sp = get_stockpile(id); if (!sp) { - out.printerr("Specified building isn't a stockpile: %d.\n", id); + out.printerr("Specified building isn't a stockpile: {}\n", id); return false; } @@ -95,7 +95,7 @@ static bool stockpiles_import(color_ostream& out, string fname, int id, string m fname += ".dfstock"; if (!Filesystem::exists(fname)) { - out.printerr("ERROR: file doesn't exist: '%s'\n", fname.c_str()); + out.printerr("ERROR: file doesn't exist: '{}'\n", fname); return false; } @@ -111,12 +111,12 @@ static bool stockpiles_import(color_ostream& out, string fname, int id, string m try { StockpileSerializer cereal(sp); if (!cereal.unserialize_from_file(out, fname, mode, filters)) { - out.printerr("deserialization failed: '%s'\n", fname.c_str()); + out.printerr("deserialization failed: '{}'\n", fname); return false; } } catch (std::exception& e) { - out.printerr("deserialization failed: protobuf exception: %s\n", e.what()); + out.printerr("deserialization failed: protobuf exception: {}\n", e.what()); return false; } @@ -126,13 +126,13 @@ static bool stockpiles_import(color_ostream& out, string fname, int id, string m static df::stockpile_settings * get_stop_settings(color_ostream& out, int route_id, int stop_id) { auto route = df::hauling_route::find(route_id); if (!route) { - out.printerr("Specified hauling route not found: %d.\n", route_id); + out.printerr("Specified hauling route not found: {}\n", route_id); return NULL; } df::hauling_stop *stop = binsearch_in_vector(route->stops, &df::hauling_stop::id, stop_id); if (!stop) { - out.printerr("Specified hauling stop not found in route %d: %d.\n", route_id, stop_id); + out.printerr("Specified hauling stop not found in route {}: {}.\n", route_id, stop_id); return NULL; } @@ -150,12 +150,12 @@ static bool stockpiles_route_export(color_ostream& out, string fname, int route_ try { StockpileSettingsSerializer cereal(settings); if (!cereal.serialize_to_file(out, fname, includedElements)) { - out.printerr("could not save to '%s'\n", fname.c_str()); + out.printerr("could not save to '{}'\n", fname); return false; } } catch (std::exception& e) { - out.printerr("serialization failed: protobuf exception: %s\n", e.what()); + out.printerr("serialization failed: protobuf exception: {}\n", e.what()); return false; } @@ -171,7 +171,7 @@ static bool stockpiles_route_import(color_ostream& out, string fname, int route_ fname += ".dfstock"; if (!Filesystem::exists(fname)) { - out.printerr("ERROR: file doesn't exist: '%s'\n", fname.c_str()); + out.printerr("ERROR: file doesn't exist: '{}'\n", fname); return false; } @@ -187,12 +187,12 @@ static bool stockpiles_route_import(color_ostream& out, string fname, int route_ try { StockpileSettingsSerializer cereal(settings); if (!cereal.unserialize_from_file(out, fname, mode, filters)) { - out.printerr("deserialization failed: '%s'\n", fname.c_str()); + out.printerr("deserialization failed: '{}'\n", fname); return false; } } catch (std::exception& e) { - out.printerr("deserialization failed: protobuf exception: %s\n", e.what()); + out.printerr("deserialization failed: protobuf exception: {}\n", e.what()); return false; } diff --git a/plugins/stonesense b/plugins/stonesense index 08102c59ef..de2cd37da6 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 08102c59ef1c76b1cebe3e582be51f0ac763de0e +Subproject commit de2cd37da6e64d8b53523e873889b82b6182b6a5 diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 3bdd9431df..2ab221195d 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -149,7 +149,7 @@ int getCreatedMetalBars (int32_t idx) command_result df_strangemood (color_ostream &out, vector & parameters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -202,7 +202,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) type = mood_type::Macabre; else { - out.printerr("Mood type '%s' not recognized!\n", parameters[i].c_str()); + out.printerr("Mood type '{}' not recognized!\n", parameters[i]); return CR_WRONG_USAGE; } } @@ -260,13 +260,13 @@ command_result df_strangemood (color_ostream &out, vector & parameters) skill = job_skill::MECHANICS; else { - out.printerr("Mood skill '%s' not recognized!\n", parameters[i].c_str()); + out.printerr("Mood skill '{}' not recognized!\n", parameters[i]); return CR_WRONG_USAGE; } } else { - out.printerr("Unrecognized parameter: %s\n", parameters[i].c_str()); + out.printerr("Unrecognized parameter: {}\n", parameters[i]); return CR_WRONG_USAGE; } } @@ -428,7 +428,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) if (unit->job.current_job) { // TODO: cancel job - out.printerr("Chosen unit '%s' has active job, cannot start mood!\n", + out.printerr("Chosen unit '{}' has active job, cannot start mood!\n", DF2CONSOLE(Units::getReadableName(unit)).c_str()); return CR_FAILURE; } diff --git a/plugins/suspendmanager.cpp b/plugins/suspendmanager.cpp index d63d6a99c9..13a37d1901 100644 --- a/plugins/suspendmanager.cpp +++ b/plugins/suspendmanager.cpp @@ -113,11 +113,13 @@ using df::coord; // impassible constructions static const std::bitset<64> construction_impassible = std::bitset<64>() .set(construction_type::Wall) + .set(construction_type::ReinforcedWall) .set(construction_type::Fortification); // constructions requiring same support as walls static const std::bitset<64> construction_wall_support = std::bitset<64>() .set(construction_type::Wall) + .set(construction_type::ReinforcedWall) .set(construction_type::Fortification) .set(construction_type::UpStair) .set(construction_type::UpDownStair); @@ -395,7 +397,8 @@ class SuspendManager { // other tiles can become suitable if a wall is being constructed below auto below = Buildings::findAtTile(coord(pos.x,pos.y,pos.z-1)); if (below && below->getType() == df::building_type::Construction && - below->getSubtype() == construction_type::Wall) + (below->getSubtype() == construction_type::Wall || + below->getSubtype() == construction_type::ReinforcedWall)) return true; return false; @@ -468,7 +471,7 @@ class SuspendManager { static bool riskBlocking(color_ostream &out, df::job* job) { if (job->job_type != job_type::ConstructBuilding) return false; - TRACE(cycle,out).print("risk blocking: check construction job %d\n", job->id); + TRACE(cycle,out).print("risk blocking: check construction job {}\n", job->id); auto building = Job::getHolder(job); if (!building || !isImpassable(building)) @@ -481,7 +484,7 @@ class SuspendManager { return false; auto risk = riskOfStuckConstructionAt(pos); - TRACE(cycle,out).print(" risk is %d\n", risk); + TRACE(cycle,out).print(" risk is {}\n", risk); // TOTHINK: on a large grid, this will compute the same risk up to 5 times for (auto npos : neighbors | transform(around(pos))) { @@ -501,7 +504,7 @@ class SuspendManager { if (!building || building->getType() != df::building_type::Construction) return false; - TRACE(cycle,out).print("check (construction) construction job %d for support\n", job->id); + TRACE(cycle,out).print("check (construction) construction job {} for support\n", job->id); coord pos(building->centerx,building->centery,building->z); @@ -644,7 +647,7 @@ class SuspendManager { void refresh(color_ostream &out) { - DEBUG(cycle,out).print("starting refresh, prevent blocking is %s\n", + DEBUG(cycle,out).print("starting refresh, prevent blocking is {}\n", prevent_blocking ? "true" : "false"); suspensions.clear(); leadsToDeadend.clear(); @@ -690,7 +693,7 @@ class SuspendManager { if (building && buildingOnDesignation(building)) suspensions[job->id]=Reason::ERASE_DESIGNATION; } - DEBUG(cycle,out).print("finished refresh: found %zu reasons for suspension\n",suspensions.size()); + DEBUG(cycle,out).print("finished refresh: found {} reasons for suspension\n",suspensions.size()); } void do_cycle (color_ostream &out, bool unsuspend_everything = false) @@ -717,7 +720,7 @@ class SuspendManager { } } } - DEBUG(cycle,out).print("suspended %zu constructions and unsuspended %zu constructions\n", + DEBUG(cycle,out).print("suspended {} constructions and unsuspended {} constructions\n", num_suspend, num_unsuspend); } @@ -769,7 +772,7 @@ static void do_cycle(color_ostream &out); static void jobCompletedHandler(color_ostream& out, void* ptr); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(control,out).print("initializing %s\n", plugin_name); + DEBUG(control,out).print("initializing {}\n", plugin_name); suspendmanager_instance = std::make_unique(); eventhandler_instance = std::make_unique(plugin_self,jobCompletedHandler,1); @@ -790,13 +793,13 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector prevent_blocking = config.get_bool(CONFIG_PREVENT_BLOCKING); - DEBUG(control,out).print("loading persisted state: enabled is %s / prevent_blocking is %s\n", + DEBUG(control,out).print("loading persisted state: enabled is {} / prevent_blocking is {}\n", is_enabled ? "true" : "false", suspendmanager_instance->prevent_blocking ? "true" : "false"); if(is_enabled) { @@ -851,7 +854,7 @@ DFhackCExport command_result plugin_load_site_data (color_ostream &out) { DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { if (event == DFHack::SC_WORLD_UNLOADED) { if (is_enabled) { - DEBUG(control,out).print("world unloaded; disabling %s\n", + DEBUG(control,out).print("world unloaded; disabling {}\n", plugin_name); is_enabled = false; } @@ -867,16 +870,16 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { static command_result do_command(color_ostream &out, vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } if (parameters.size() == 0) { if (!is_enabled) { - out.print("%s is disabled\n", plugin_name); + out.print("{} is disabled\n", plugin_name); } else { out.print( - "%s is enabled %s supending blocking jobs\n", plugin_name, + "{} is enabled {} suspending blocking jobs\n", plugin_name, suspendmanager_instance->prevent_blocking ? "and" : "but not"); } return CR_OK; @@ -893,7 +896,7 @@ static command_result do_command(color_ostream &out, vector ¶meters) config.set_bool(CONFIG_PREVENT_BLOCKING, true); if (is_enabled) { do_cycle(out); - out.print("%s", suspendmanager_instance->getStatus(out).c_str()); + out.print("{}", suspendmanager_instance->getStatus(out)); } return CR_OK; } else if (parameters[2] == "false") { @@ -901,7 +904,7 @@ static command_result do_command(color_ostream &out, vector ¶meters) config.set_bool(CONFIG_PREVENT_BLOCKING, false); if (is_enabled) { do_cycle(out); - out.print("%s", suspendmanager_instance->getStatus(out).c_str()); + out.print("{}", suspendmanager_instance->getStatus(out)); } return CR_OK; } else @@ -920,7 +923,7 @@ static void jobCompletedHandler(color_ostream& out, void* ptr) { TRACE(cycle,out).print("job completed/initiated handler called\n"); df::job* job = static_cast(ptr); if (SuspendManager::isConstructionJob(job)) { - DEBUG(cycle,out).print("construction job initiated/completed (tick: %d)\n", world->frame_counter); + DEBUG(cycle,out).print("construction job initiated/completed (tick: {})\n", world->frame_counter); cycle_needed = true; } @@ -935,7 +938,7 @@ static void do_cycle(color_ostream &out) { cycle_timestamp = world->frame_counter; cycle_needed = false; - DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + DEBUG(cycle,out).print("running {} cycle\n", plugin_name); suspendmanager_instance->do_cycle(out); } diff --git a/plugins/tailor.cpp b/plugins/tailor.cpp index c66d314431..1018999ec4 100644 --- a/plugins/tailor.cpp +++ b/plugins/tailor.cpp @@ -228,8 +228,8 @@ class Tailor { df::item_type t; int size; std::tie(t, size) = i.first; - DEBUG(cycle).print("tailor: %d %s of size %d found\n", - i.second, ENUM_KEY_STR(item_type, t).c_str(), size); + DEBUG(cycle).print("tailor: {} {} of size {} found\n", + i.second, ENUM_KEY_STR(item_type, t), size); } } } @@ -248,7 +248,7 @@ class Tailor { // only count dyed std::string d; i->getItemDescription(&d, 0); - TRACE(cycle).print("tailor: skipping undyed %s\n", DF2CONSOLE(d).c_str()); + TRACE(cycle).print("tailor: skipping undyed {}\n", DF2CONSOLE(d)); continue; } MaterialInfo mat(i); @@ -268,7 +268,7 @@ class Tailor { { std::string d; i->getItemDescription(&d, 0); - DEBUG(cycle).print("tailor: weird cloth item found: %s (%d)\n", DF2CONSOLE(d).c_str(), i->id); + DEBUG(cycle).print("tailor: weird cloth item found: {} ({})\n", DF2CONSOLE(d), i->id); } } } @@ -280,7 +280,7 @@ class Tailor { supply[M_LEATHER] += i->getStackSize(); } - DEBUG(cycle).print("tailor: available silk %d yarn %d cloth %d leather %d adamantine %d\n", + DEBUG(cycle).print("tailor: available silk {} yarn {} cloth {} leather {} adamantine {}\n", supply[M_SILK], supply[M_YARN], supply[M_CLOTH], supply[M_LEATHER], supply[M_ADAMANTINE]); } @@ -331,9 +331,9 @@ class Tailor { { if (ordered.count(ty) == 0) { - DEBUG(cycle).print ("tailor: %s (size %d) worn by %s (size %d) needs replacement\n", - DF2CONSOLE(description).c_str(), isize, - DF2CONSOLE(Units::getReadableName(u)).c_str(), usize); + DEBUG(cycle).print ("tailor: {} (size {}) worn by {} (size {}) needs replacement\n", + DF2CONSOLE(description), isize, + DF2CONSOLE(Units::getReadableName(u)), usize); needed[std::make_pair(ty, usize)] += 1; ordered.insert(ty); } @@ -346,10 +346,10 @@ class Tailor { bool confiscated = Items::setOwner(w, NULL); INFO(cycle).print( - "tailor: %s %s from %s.\n", + "tailor: {} {} from {}.\n", (confiscated ? "confiscated" : "could not confiscate"), - DF2CONSOLE(description).c_str(), - DF2CONSOLE(Units::getReadableName(u)).c_str() + DF2CONSOLE(description), + DF2CONSOLE(Units::getReadableName(u)) ); } @@ -363,10 +363,10 @@ class Tailor { { if (equipped.count(ty) == 0 && ordered.count(ty) == 0) { - TRACE(cycle).print("tailor: one %s of size %d needed to cover %s\n", - ENUM_KEY_STR(item_type, ty).c_str(), + TRACE(cycle).print("tailor: one {} of size {} needed to cover {}\n", + ENUM_KEY_STR(item_type, ty), usize, - DF2CONSOLE(Units::getReadableName(u)).c_str()); + DF2CONSOLE(Units::getReadableName(u))); needed[std::make_pair(ty, usize)] += 1; } } @@ -423,7 +423,7 @@ class Tailor { { const df::job_type j = jj->second; orders[std::make_tuple(j, sub, size)] += count; - DEBUG(cycle).print("tailor: %s times %d of size %d ordered\n", ENUM_KEY_STR(job_type, j).c_str(), count, size); + DEBUG(cycle).print("tailor: {} times {} of size {} ordered\n", ENUM_KEY_STR(job_type, j), count, size); } } } @@ -441,8 +441,8 @@ class Tailor { if (o->material_category.whole == m.job_material.whole) { supply[m] -= o->amount_left; - TRACE(cycle).print("tailor: supply of %s reduced by %d due to being required for an existing order\n", - DF2CONSOLE(m.name).c_str(), o->amount_left); + TRACE(cycle).print("tailor: supply of {} reduced by {} due to being required for an existing order\n", + DF2CONSOLE(m.name), o->amount_left); } } } @@ -574,7 +574,7 @@ class Tailor { auto [_, n] = get_or_create_order(c, df::job_type::CustomReaction, -1, -1, 0, r->code); if (n > 0) { - INFO(cycle).print("tailor: ordered %d %s\n", c, DF2CONSOLE(descr).c_str()); + INFO(cycle).print("tailor: ordered {} {}\n", c, DF2CONSOLE(descr)); } return n; } @@ -663,6 +663,7 @@ class Tailor { order->amount_total = c; order->status.bits.validated = false; order->status.bits.active = false; + order->frequency = df::workquota_frequency_type::OneTime; order->id = world->manager_orders.manager_order_next_id++; world->manager_orders.all.push_back(order); @@ -687,7 +688,7 @@ class Tailor { if (sizes.count(size) == 0) { - WARN(cycle).print("tailor: cannot determine race for clothing of size %d, skipped\n", + WARN(cycle).print("tailor: cannot determine race for clothing of size {}, skipped\n", size); continue; } @@ -738,11 +739,11 @@ class Tailor { if (!can_make) { - INFO(cycle).print("tailor: civilization cannot make %s, skipped\n", DF2CONSOLE(name_p).c_str()); + INFO(cycle).print("tailor: civilization cannot make {}, skipped\n", DF2CONSOLE(name_p)); continue; } - DEBUG(cycle).print("tailor: ordering %d %s\n", count, DF2CONSOLE(name_p).c_str()); + DEBUG(cycle).print("tailor: ordering {} {}\n", count, DF2CONSOLE(name_p)); for (auto& m : material_order) { @@ -757,8 +758,8 @@ class Tailor { if (supply[m] < count + res) { c = supply[m] - res; - TRACE(cycle).print("tailor: order reduced from %d to %d to protect reserves of %s\n", - count, c, DF2CONSOLE(m.name).c_str()); + TRACE(cycle).print("tailor: order reduced from {} to {} to protect reserves of {}\n", + count, c, DF2CONSOLE(m.name)); skipped += (count - c); } supply[m] -= c; @@ -767,13 +768,12 @@ class Tailor { if (n > 0) { - INFO(cycle).print("tailor: added order #%d for %d %s %s, sized for %s\n", + INFO(cycle).print("tailor: added order #{} for {} {} {}, sized for {}\n", order->id, n, - bitfield_to_string(order->material_category).c_str(), - DF2CONSOLE((c > 1) ? name_p : name_s).c_str(), - DF2CONSOLE(world->raws.creatures.all[order->specdata.hist_figure_id]->name[1]).c_str() - ); + bitfield_to_string(order->material_category), + DF2CONSOLE((c > 1) ? name_p : name_s), + DF2CONSOLE(world->raws.creatures.all[order->specdata.hist_figure_id]->name[1])); count -= n; ordered += n; @@ -782,7 +782,7 @@ class Tailor { else { skipped += count; - DEBUG(cycle).print("tailor: material %s skipped due to lack of reserves, %d available\n", DF2CONSOLE(m.name).c_str(), supply[m]); + DEBUG(cycle).print("tailor: material {} skipped due to lack of reserves, {} available\n", DF2CONSOLE(m.name), supply[m]); } } @@ -791,7 +791,7 @@ class Tailor { if (skipped > 0) { - INFO(cycle).print("tailor: %d item%s not ordered due to a lack of materials\n", skipped, skipped != 1 ? "s" : ""); + INFO(cycle).print("tailor: {} item{} not ordered due to a lack of materials\n", skipped, skipped != 1 ? "s" : ""); if (automate_dye) { @@ -799,23 +799,23 @@ class Tailor { int available_dyeable_cloth = count_dyeables(df::item_type::CLOTH); int dye_cloth_orders = count_dye_cloth_orders(); - DEBUG(cycle).print("tailor: available dyes = %d, available dyeable cloth = %d, dye cloth orders = %d\n", + DEBUG(cycle).print("tailor: available dyes = {}, available dyeable cloth = {}, dye cloth orders = {}\n", available_dyes, available_dyeable_cloth, dye_cloth_orders); int to_dye = std::min(skipped, std::min(available_dyes, available_dyeable_cloth) - dye_cloth_orders); - DEBUG(cycle).print("tailor: to dye = %d\n", to_dye); + DEBUG(cycle).print("tailor: to dye = {}\n", to_dye); if (to_dye > 0) { int dyed = order_dye_cloth(to_dye); if (dyed > 0) { - INFO(cycle).print("tailor: dyeing %d cloth\n", to_dye); + INFO(cycle).print("tailor: dyeing {} cloth\n", to_dye); } } int dyes_to_make = available_dyes - to_dye; if (dyes_to_make > 0) { - INFO(cycle).print("tailor: ordering up to %d dyes\n", dyes_to_make); + INFO(cycle).print("tailor: ordering up to {} dyes\n", dyes_to_make); make_dyes(dyes_to_make); } } @@ -843,7 +843,7 @@ static command_result do_command(color_ostream &out, vector ¶meters) static int do_cycle(color_ostream &out); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(control,out).print("initializing %s\n", plugin_name); + DEBUG(control,out).print("initializing {}\n", plugin_name); tailor_instance = std::make_unique(); @@ -858,19 +858,19 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector set_confiscate(config.get_bool(CONFIG_CONFISCATE)); - DEBUG(control,out).print("loading persisted confiscation state: %s\n", + DEBUG(control,out).print("loading persisted confiscation state: {}\n", tailor_instance->get_confiscate() ? "true" : "false"); tailor_instance->set_automate_dye(config2.get_bool(CONFIG_AUTOMATE_DYE)); - DEBUG(control, out).print("loading persisted dye automation state: %s\n", + DEBUG(control, out).print("loading persisted dye automation state: {}\n", tailor_instance->get_automate_dye() ? "true" : "false"); tailor_instance->sync_material_order(); @@ -933,7 +933,7 @@ DFhackCExport command_result plugin_load_site_data (color_ostream &out) { DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { if (event == DFHack::SC_WORLD_UNLOADED) { if (is_enabled) { - DEBUG(control,out).print("world unloaded; disabling %s\n", + DEBUG(control,out).print("world unloaded; disabling {}\n", plugin_name); is_enabled = false; } @@ -945,14 +945,14 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { if (world->frame_counter - cycle_timestamp >= CYCLE_TICKS) { int ordered = do_cycle(out); if (0 < ordered) - out.print("tailor: ordered %d items of clothing\n", ordered); + out.print("tailor: ordered {} items of clothing\n", ordered); } return CR_OK; } static command_result do_command(color_ostream &out, vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -975,7 +975,7 @@ static int do_cycle(color_ostream &out) { // mark that we have recently run cycle_timestamp = world->frame_counter; - DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + DEBUG(cycle,out).print("running {} cycle\n", plugin_name); return tailor_instance->do_cycle(); } @@ -986,7 +986,7 @@ static int do_cycle(color_ostream &out) { static void tailor_doCycle(color_ostream &out) { DEBUG(control,out).print("entering tailor_doCycle\n"); - out.print("ordered %d items of clothing\n", do_cycle(out)); + out.print("ordered {} items of clothing\n", do_cycle(out)); } // remember, these are ONE-based indices from Lua @@ -1008,7 +1008,7 @@ static void tailor_setMaterialPreferences(color_ostream &out, int32_t silkIdx, static void tailor_setConfiscate(color_ostream& out, bool enable) { - DEBUG(control,out).print("%s confiscation of tattered clothing \n", enable ? "enabling" : "disabling"); + DEBUG(control,out).print("{} confiscation of tattered clothing \n", enable ? "enabling" : "disabling"); config.set_bool(CONFIG_CONFISCATE, enable); tailor_instance->set_confiscate(enable); } @@ -1020,7 +1020,7 @@ static bool tailor_getConfiscate(color_ostream& out) static void tailor_setAutomateDye(color_ostream& out, bool enable) { - DEBUG(control, out).print("%s automation of dye\n", enable ? "enabling" : "disabling"); + DEBUG(control, out).print("{} automation of dye\n", enable ? "enabling" : "disabling"); config2.set_bool(CONFIG_AUTOMATE_DYE, enable); tailor_instance->set_automate_dye(enable); } diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index 95c5bd4424..dc93bea4bb 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -82,7 +82,8 @@ static const std::map, df::til }; static const uint16_t UNDERGROUND_TEMP = 10015; -static const char * HISTORY_FILE = "dfhack-config/tiletypes.history"; +static const std::filesystem::path get_history_file() { return Core::getInstance().getConfigPath() / "tiletypes.history"; } + CommandHistory tiletypes_hist; command_result df_tiletypes (color_ostream &out, vector & parameters); @@ -92,7 +93,7 @@ command_result df_tiletypes_here_point (color_ostream &out, vector & pa DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - tiletypes_hist.load(HISTORY_FILE); + tiletypes_hist.load(get_history_file()); commands.push_back(PluginCommand("tiletypes", "Paints tiles of specified types onto the map.", df_tiletypes, true, true)); commands.push_back(PluginCommand("tiletypes-command", "Run tiletypes commands (seperated by ' ; ')", df_tiletypes_command)); commands.push_back(PluginCommand("tiletypes-here", "Repeat tiletypes command at cursor (with brush)", df_tiletypes_here)); @@ -102,7 +103,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector 0) - out.printerr("Could not update %d tiles of %zu.\n", failures, all_tiles.size()); + out.printerr("Could not update {} tiles of {}.\n", failures, all_tiles.size()); else if (!opts.quiet) - out.print("Processed %zu tiles.\n", all_tiles.size()); + out.print("Processed {} tiles.\n", all_tiles.size()); if (map.WriteAll()) { @@ -1399,57 +1400,57 @@ command_result df_tiletypes_here_point (color_ostream &out, vector & pa static bool setTile(color_ostream& out, df::coord pos, TileType target) { if (!Maps::isValidTilePos(pos)) { - out.printerr("Invalid map position: %d, %d, %d\n", pos.x, pos.y, pos.z); + out.printerr("Invalid map position: ({}, {}, {})\n", pos.x, pos.y, pos.z); return false; } if (!is_valid_enum_item(target.shape)) { - out.printerr("Invalid shape type: %d\n", target.shape); + out.printerr("Invalid shape type: {}\n", ENUM_AS_STR(target.shape)); return false; } if (!is_valid_enum_item(target.material)) { - out.printerr("Invalid material type: %d\n", target.material); + out.printerr("Invalid material type: {}\n", ENUM_AS_STR(target.material)); return false; } if (!is_valid_enum_item(target.special)) { - out.printerr("Invalid special type: %d\n", target.special); + out.printerr("Invalid special type: {}\n", ENUM_AS_STR(target.special)); return false; } if (!is_valid_enum_item(target.variant)) { - out.printerr("Invalid variant type: %d\n", target.variant); + out.printerr("Invalid variant type: {}\n", ENUM_AS_STR(target.variant)); return false; } if (target.hidden < -1 || target.hidden > 1) { - out.printerr("Invalid hidden value: %d\n", target.hidden); + out.printerr("Invalid hidden value: {}\n", target.hidden); return false; } if (target.light < -1 || target.light > 1) { - out.printerr("Invalid light value: %d\n", target.light); + out.printerr("Invalid light value: {}\n", target.light); return false; } if (target.subterranean < -1 || target.subterranean > 1) { - out.printerr("Invalid subterranean value: %d\n", target.subterranean); + out.printerr("Invalid subterranean value: {}\n", target.subterranean); return false; } if (target.skyview < -1 || target.skyview > 1) { - out.printerr("Invalid skyview value: %d\n", target.skyview); + out.printerr("Invalid skyview value: {}\n", target.skyview); return false; } if (target.aquifer < -1 || target.aquifer > 2) { - out.printerr("Invalid aquifer value: %d\n", target.aquifer); + out.printerr("Invalid aquifer value: {}\n", target.aquifer); return false; } if (target.autocorrect < 0 || target.autocorrect > 1) { - out.printerr("Invalid autocorrect value: %d\n", target.autocorrect); + out.printerr("Invalid autocorrect value: {}\n", target.autocorrect); return false; } if (target.material == df::tiletype_material::STONE) { if (target.stone_material != -1 && !isStoneInorganic(target.stone_material)) { - out.printerr("Invalid stone material: %d\n", target.stone_material); + out.printerr("Invalid stone material: {}\n", target.stone_material); return false; } if (!is_valid_enum_item(target.vein_type)) { - out.printerr("Invalid vein type: %d\n", target.vein_type); + out.printerr("Invalid vein type: {}\n", ENUM_AS_STR(target.vein_type)); return false; } } diff --git a/plugins/timestream.cpp b/plugins/timestream.cpp index a5e5b04c80..d7dca161cb 100644 --- a/plugins/timestream.cpp +++ b/plugins/timestream.cpp @@ -33,12 +33,18 @@ #include "df/building_nest_boxst.h" #include "df/building_trapst.h" #include "df/buildingitemst.h" +#include "df/caravan_state.h" +#include "df/flow_info.h" +#include "df/flow_type.h" #include "df/init.h" #include "df/item_eggst.h" +#include "df/plotinfost.h" #include "df/unit.h" #include "df/world.h" #include +#include +#include using std::string; using std::vector; @@ -52,6 +58,8 @@ REQUIRE_GLOBAL(cur_year_tick); REQUIRE_GLOBAL(cur_year_tick_advmode); REQUIRE_GLOBAL(init); REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(flows); +REQUIRE_GLOBAL(plotinfo); namespace DFHack { // for configuration-related logging @@ -78,7 +86,7 @@ static void on_new_active_unit(color_ostream& out, void* data); static void do_cycle(color_ostream &out); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(control,out).print("initializing %s\n", plugin_name); + DEBUG(control,out).print("initializing {}\n", plugin_name); commands.push_back(PluginCommand( plugin_name, @@ -162,7 +170,7 @@ static void do_disable() { DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -170,7 +178,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (enable != is_enabled) { is_enabled = enable; - DEBUG(control,out).print("%s from the API; persisting\n", + DEBUG(control,out).print("{} from the API; persisting\n", is_enabled ? "enabled" : "disabled"); config.set_bool(CONFIG_IS_ENABLED, is_enabled); if (enable) { @@ -181,7 +189,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { do_disable(); } } else { - DEBUG(control,out).print("%s from the API, but already %s; no action\n", + DEBUG(control,out).print("{} from the API, but already {}; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); } @@ -189,7 +197,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { } DFhackCExport command_result plugin_shutdown(color_ostream &out) { - DEBUG(control,out).print("shutting down %s\n", plugin_name); + DEBUG(control,out).print("shutting down {}\n", plugin_name); return CR_OK; } @@ -221,7 +229,7 @@ DFhackCExport command_result plugin_load_site_data(color_ostream &out) { if (config.get_bool(CONFIG_IS_ENABLED)) { plugin_enable(out, true); } - DEBUG(control,out).print("loading persisted enabled state: %s\n", + DEBUG(control,out).print("loading persisted enabled state: {}\n", is_enabled ? "true" : "false"); return CR_OK; @@ -230,7 +238,7 @@ DFhackCExport command_result plugin_load_site_data(color_ostream &out) { DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { if (event == DFHack::SC_WORLD_UNLOADED) { if (is_enabled) { - DEBUG(control,out).print("world unloaded; disabling %s\n", + DEBUG(control,out).print("world unloaded; disabling {}\n", plugin_name); do_disable(); } @@ -246,7 +254,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { static command_result do_command(color_ostream &out, vector ¶meters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } @@ -270,7 +278,7 @@ static void record_coverage(color_ostream &out) { if (coverage_slot >= NUM_COVERAGE_TICKS) return; if (!tick_coverage[coverage_slot]) { - DEBUG(cycle,out).print("recording coverage for slot: %u", coverage_slot); + DEBUG(cycle,out).print("recording coverage for slot: {}", coverage_slot); } tick_coverage[coverage_slot] = true; } @@ -314,12 +322,91 @@ static int32_t clamp_coverage(int32_t timeskip) { return timeskip; } +using df_tick_type = std::remove_reference_t; + +static df_tick_type flow_next_required_tick(int flow_index) +{ + using namespace df::enums::flow_type; + + const auto flow = (*flows)[flow_index]; + + if (flow == nullptr || flow->flags.bits.DEAD) + return std::numeric_limits::max(); + + const auto cur_tick = *cur_year_tick; + + struct update_parameters_t { + int speed; + int cycle; + }; + + const auto [speed, cycle] = [flow] () -> update_parameters_t { + switch (flow->type) + { + case ItemCloud: + case MaterialDust: + case MaterialGas: + case MaterialVapor: + if (flow->flags.bits.CREEPING) + return update_parameters_t{10, 100}; + else + return update_parameters_t{1, 5}; + case Dragonfire: + case Fire: + case Web: + return update_parameters_t{1, 3}; + default: + return update_parameters_t{10, 100}; + } + }(); + + const int stride = cycle / speed; + + const int phase = (flow_index % stride) * speed; + + const int cur_phase = cur_tick % cycle; + + return cur_tick + (phase - cur_phase) + ((cur_phase > phase) ? cycle : 0); +} + +static df_tick_type flows_next_required_tick() { + if (flows == nullptr || flows->empty()) + return std::numeric_limits::max(); + + auto flow_indices = std::views::iota(0, (int)flows->size()); + auto next_flow = std::ranges::min(flow_indices, {}, flow_next_required_tick); + + return flow_next_required_tick(next_flow); +} + +static bool detect_caravans() +{ + auto& caravans = df::global::plotinfo->caravans; + return std::any_of(caravans.begin(), caravans.end(), [](auto caravan) { + if (caravan->trade_state != df::caravan_state::T_trade_state::AtDepot) + return false; + auto car_civ = caravan->entity; + auto& units = world->units.active; + return std::any_of(units.begin(), units.end(), [car_civ] (auto un) { + return (un->civ_id == car_civ + && DFHack::Units::isMerchant(un) + && std::any_of(un->inventory.begin(), un->inventory.end(), [] (auto inv_item) { + return inv_item->item && inv_item->item->flags.bits.trader; + })); + }); + }); +} + static int32_t clamp_timeskip(int32_t timeskip) { if (timeskip <= 0) return 0; + // timeskip cannot be applied when caravans are loading/unloading because we don't know how to jog that timer + if (detect_caravans()) + return 0; int32_t next_tick = *cur_year_tick + 1; timeskip = std::min(timeskip, get_next_trigger_year_tick(next_tick) - next_tick); timeskip = std::min(timeskip, get_next_birthday(next_tick) - next_tick); + timeskip = std::min(timeskip, flows_next_required_tick() - next_tick); return clamp_coverage(timeskip); } @@ -616,7 +703,7 @@ static void adjust_items(color_ostream &out, int32_t timeskip) { } static void do_cycle(color_ostream &out) { - DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + DEBUG(cycle,out).print("running {} cycle\n", plugin_name); // mark that we have recently run cycle_timestamp = world->frame_counter; @@ -647,7 +734,7 @@ static void do_cycle(color_ostream &out) { // don't let our deficit grow unbounded if we can never catch up timeskip_deficit = std::min(desired_timeskip - float(timeskip), 100.0F); - DEBUG(cycle,out).print("cur_year_tick: %d, real_fps: %d, timeskip: (%d, +%.2f)\n", *cur_year_tick, real_fps, timeskip, timeskip_deficit); + DEBUG(cycle,out).print("cur_year_tick: {}, real_fps: {}, timeskip: ({}, +{:.2f})\n", *cur_year_tick, real_fps, timeskip, timeskip_deficit); if (timeskip <= 0) return; @@ -669,7 +756,7 @@ static void on_new_active_unit(color_ostream& out, void* data) { auto unit = df::unit::find(unit_id); if (!unit) return; - DEBUG(event,out).print("registering new unit %d (%s)\n", unit->id, Units::getReadableName(unit).c_str()); + DEBUG(event,out).print("registering new unit {} ({})\n", unit->id, Units::getReadableName(unit)); register_birthday(unit); } @@ -678,7 +765,7 @@ static void on_new_active_unit(color_ostream& out, void* data) { // static void timestream_setFps(color_ostream &out, int fps) { - DEBUG(cycle,out).print("timestream_setFps: %d\n", fps); + DEBUG(cycle,out).print("timestream_setFps: {}\n", fps); config.set_int(CONFIG_TARGET_FPS, clamp_fps_to_valid(fps)); } diff --git a/plugins/tubefill.cpp b/plugins/tubefill.cpp index 46f7101fab..cab0241bd4 100644 --- a/plugins/tubefill.cpp +++ b/plugins/tubefill.cpp @@ -116,6 +116,6 @@ command_result tubefill(color_ostream &out, std::vector & params) } } } - out.print("Found and changed %" PRId64 " tiles.\n", count); + out.print("Found and changed {} tiles.\n", count); return CR_OK; } diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index dd7edbf3e1..eeed5027a8 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -16,6 +16,7 @@ using namespace DFHack; #include "tweaks/adamantine-cloth-wear.h" #include "tweaks/animaltrap-reuse.h" #include "tweaks/craft-age-wear.h" +#include "tweaks/drawbridge-tiles.h" #include "tweaks/eggs-fertile.h" #include "tweaks/fast-heat.h" #include "tweaks/flask-contents.h" @@ -58,6 +59,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, vector ¶meters) { if (parameters.empty() || parameters[0] == "list") { out.print("tweaks:\n"); for (auto & entry : get_status()) - out.print(" %25s: %s\n", entry.first.c_str(), entry.second ? "enabled" : "disabled"); + out.print(" {:25}: {}\n", entry.first, entry.second ? "enabled" : "disabled"); return CR_OK; } diff --git a/plugins/tweak/tweaks/drawbridge-tiles.h b/plugins/tweak/tweaks/drawbridge-tiles.h new file mode 100644 index 0000000000..287c248924 --- /dev/null +++ b/plugins/tweak/tweaks/drawbridge-tiles.h @@ -0,0 +1,60 @@ +// Make raised drawbridge tiles indicate the bridge's direction + +#include "df/building_drawbuffer.h" +#include "df/building_bridgest.h" + +struct drawbridge_tiles_hook : df::building_bridgest { + typedef df::building_bridgest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (uint32_t curtick, df::building_drawbuffer *buf, int16_t z_offset)) + { + static const unsigned char tiles[4][3] = + { + { 0xB7, 0xB6, 0xBD }, + { 0xD6, 0xC7, 0xD3 }, + { 0xD4, 0xCF, 0xBE }, + { 0xD5, 0xD1, 0xB8 }, + }; + INTERPOSE_NEXT(drawBuilding)(curtick, buf, z_offset); + + // Only redraw "raised" drawbridges + if (!gate_flags.bits.raised || direction == -1) + return; + + // Figure out the extents and the axis + int p1, p2; + bool iy = false; + switch (direction) + { + case 0: // Left + case 1: // Right + p1 = buf->y1; p2 = buf->y2; iy = true; + break; + case 2: // Up + case 3: // Down + p1 = buf->x1; p2 = buf->x2; + break; + default: + // Already ignoring retracting above + return; + } + + int x = 0, y = 0; + if (p1 == p2) + buf->tile[0][0] = tiles[direction][1]; + else for (int p = p1; p <= p2; p++) + { + if (p == p1) + buf->tile[x][y] = tiles[direction][0]; + else if (p == p2) + buf->tile[x][y] = tiles[direction][2]; + else + buf->tile[x][y] = tiles[direction][1]; + if (iy) + y++; + else + x++; + } + } +}; +IMPLEMENT_VMETHOD_INTERPOSE(drawbridge_tiles_hook, drawBuilding); diff --git a/plugins/work-now.cpp b/plugins/work-now.cpp index ca9f1ffe06..6844a23623 100644 --- a/plugins/work-now.cpp +++ b/plugins/work-now.cpp @@ -44,13 +44,13 @@ static void cleanup() { DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } if (enable != is_enabled) { is_enabled = enable; - DEBUG(log,out).print("%s from the API; persisting\n", + DEBUG(log,out).print("{} from the API; persisting\n", is_enabled ? "enabled" : "disabled"); config.set_bool(CONFIG_IS_ENABLED, is_enabled); if (enable) @@ -58,7 +58,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { else cleanup(); } else { - DEBUG(log,out).print("%s from the API, but already %s; no action\n", + DEBUG(log,out).print("{} from the API, but already {} ; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); } @@ -81,7 +81,7 @@ DFhackCExport command_result plugin_load_site_data (color_ostream &out) { } is_enabled = config.get_bool(CONFIG_IS_ENABLED); - DEBUG(log,out).print("loading persisted enabled state: %s\n", + DEBUG(log,out).print("loading persisted enabled state: {}\n", is_enabled ? "true" : "false"); return CR_OK; @@ -98,7 +98,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan poke_idlers(); } else if (e == DFHack::SC_WORLD_UNLOADED) { if (is_enabled) { - DEBUG(log,out).print("world unloaded; disabling %s\n", + DEBUG(log,out).print("world unloaded; disabling {}\n", plugin_name); is_enabled = false; } @@ -109,12 +109,12 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan static command_result work_now(color_ostream& out, vector& parameters) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { - out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } if (parameters.empty() || parameters[0] == "status") { - out.print("work_now is %sactively poking idle dwarves.\n", is_enabled ? "" : "not "); + out.print("work_now is {}actively poking idle dwarves.\n", is_enabled ? "" : "not "); return CR_OK; } diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 2b1d91b826..24c3a67a3d 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -410,7 +410,7 @@ static void stop_protect(color_ostream &out) pending_recover.clear(); if (!known_jobs.empty()) - out.print("Unprotecting %zd jobs.\n", known_jobs.size()); + out.print("Unprotecting {} jobs.\n", known_jobs.size()); for (TKnownJobs::iterator it = known_jobs.begin(); it != known_jobs.end(); ++it) delete it->second; @@ -437,7 +437,7 @@ static void start_protect(color_ostream &out) check_lost_jobs(out, 0); if (!known_jobs.empty()) - out.print("Protecting %zd jobs.\n", known_jobs.size()); + out.print("Protecting {} jobs.\n", known_jobs.size()); } static void init_state(color_ostream &out) @@ -456,7 +456,7 @@ static void init_state(color_ostream &out) if (get_constraint(out, items[i].val(), &items[i])) continue; - out.printerr("Lost constraint %s\n", items[i].val().c_str()); + out.printerr("Lost constraint {}\n", items[i].val()); World::DeletePersistentData(items[i]); } @@ -503,8 +503,8 @@ static bool recover_job(color_ostream &out, ProtectedJob *pj) pj->holder = df::building::find(pj->building_id); if (!pj->holder) { - out.printerr("Forgetting job %d (%s): holder building lost.\n", - pj->id, ENUM_KEY_STR(job_type, pj->job_copy->job_type).c_str()); + out.printerr("Forgetting job {} ({}): holder building lost.\n", + pj->id, ENUM_KEY_STR(job_type, pj->job_copy->job_type)); forget_job(out, pj); return true; } @@ -512,8 +512,8 @@ static bool recover_job(color_ostream &out, ProtectedJob *pj) // Check its state and postpone or cancel if invalid if (pj->holder->jobs.size() >= 10) { - out.printerr("Forgetting job %d (%s): holder building has too many jobs.\n", - pj->id, ENUM_KEY_STR(job_type, pj->job_copy->job_type).c_str()); + out.printerr("Forgetting job {} ({}): holder building has too many jobs.\n", + pj->id, ENUM_KEY_STR(job_type, pj->job_copy->job_type)); forget_job(out, pj); return true; } @@ -535,8 +535,8 @@ static bool recover_job(color_ostream &out, ProtectedJob *pj) { Job::deleteJobStruct(recovered); - out.printerr("Inconsistency: job %d (%s) already in list.\n", - pj->id, ENUM_KEY_STR(job_type, pj->job_copy->job_type).c_str()); + out.printerr("Inconsistency: job {} ({}) already in list.\n", + pj->id, ENUM_KEY_STR(job_type, pj->job_copy->job_type)); return true; } @@ -664,7 +664,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str if (tokens[0] == "ANY_CRAFT" || tokens[0] == "CRAFTS") { is_craft = true; } else if (!item.find(tokens[0]) || !item.isValid()) { - out.printerr("Cannot find item type: %s\n", tokens[0].c_str()); + out.printerr("Cannot find item type: {}\n", tokens[0]); return NULL; } @@ -674,7 +674,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str df::dfhack_material_category mat_mask; std::string maskstr = vector_get(tokens,1); if (!maskstr.empty() && !parseJobMaterialCategory(&mat_mask, maskstr)) { - out.printerr("Cannot decode material mask: %s\n", maskstr.c_str()); + out.printerr("Cannot decode material mask: {}\n", maskstr); return NULL; } @@ -684,7 +684,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str MaterialInfo material; std::string matstr = vector_get(tokens,2); if (!matstr.empty() && (!material.find(matstr) || !material.isValid())) { - out.printerr("Cannot find material: %s\n", matstr.c_str()); + out.printerr("Cannot find material: {}\n", matstr); return NULL; } @@ -692,7 +692,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str weight += (material.index >= 0 ? 5000 : 1000); if (mat_mask.whole && material.isValid() && !material.matches(mat_mask)) { - out.printerr("Material %s doesn't match mask %s\n", matstr.c_str(), maskstr.c_str()); + out.printerr("Material {} doesn't match mask {}\n", matstr, maskstr); return NULL; } @@ -724,7 +724,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str if (!found) { - out.printerr("Cannot parse token: %s\n", token.c_str()); + out.printerr("Cannot parse token: {}\n", token); return NULL; } } @@ -1184,9 +1184,9 @@ static void setJobResumed(color_ostream &out, ProtectedJob *pj, bool goal) if (goal != current) { - out.print("%s %s%s\n", + out.print("{} {}{}\n", (goal ? "Resuming" : "Suspending"), - shortJobDescription(pj->actual_job).c_str(), + shortJobDescription(pj->actual_job), (!goal || pj->isActuallyResumed() ? "" : " (delayed)")); } } @@ -1817,7 +1817,7 @@ static command_result workflow_cmd(color_ostream &out, vector & paramet if (deleteConstraint(parameters[1])) return CR_OK; - out.printerr("Constraint not found: %s\n", parameters[1].c_str()); + out.printerr("Constraint not found: {}\n", parameters[1]); return CR_FAILURE; } else if (cmd == "unlimit-all") diff --git a/scripts b/scripts index 539120522a..68b39325b2 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 539120522a808220e4ab23538bad5cfcd979211e +Subproject commit 68b39325b2a94e6401ab5dc32770e5ccf0f0bf52 diff --git a/test/plugins/blueprint.lua b/test/plugins/blueprint.lua index a08844bbee..d5ebd2031d 100644 --- a/test/plugins/blueprint.lua +++ b/test/plugins/blueprint.lua @@ -243,16 +243,16 @@ end function test.get_filename() local opts = {name='a', split_strategy='none'} - expect.eq('dfhack-config/blueprints/a.csv', b.get_filename(opts, 'dig', 1)) + expect.eq(dfhack.getConfigPath() .. '/blueprints/a.csv', b.get_filename(opts, 'dig', 1)) opts = {name='a/', split_strategy='none'} - expect.eq('dfhack-config/blueprints/a/a.csv', b.get_filename(opts, 'dig', 1)) + expect.eq(dfhack.getConfigPath() .. '/blueprints/a/a.csv', b.get_filename(opts, 'dig', 1)) opts = {name='a', split_strategy='phase'} - expect.eq('dfhack-config/blueprints/a-1-dig.csv', b.get_filename(opts, 'dig', 1)) + expect.eq(dfhack.getConfigPath() .. '/blueprints/a-1-dig.csv', b.get_filename(opts, 'dig', 1)) opts = {name='a/', split_strategy='phase'} - expect.eq('dfhack-config/blueprints/a/a-5-dig.csv', b.get_filename(opts, 'dig', 5)) + expect.eq(dfhack.getConfigPath() .. '/blueprints/a/a-5-dig.csv', b.get_filename(opts, 'dig', 5)) expect.error_match('could not parse basename', function() b.get_filename({name='', split_strategy='none'}) diff --git a/test/plugins/orders.lua b/test/plugins/orders.lua index ab2ad3235e..dab396ba96 100644 --- a/test/plugins/orders.lua +++ b/test/plugins/orders.lua @@ -1,7 +1,7 @@ config.mode = 'fortress' config.target = 'orders' -local FILE_PATH_PATTERN = 'dfhack-config/orders/%s.json' +local FILE_PATH_PATTERN = dfhack.getConfigPath() .. '/orders/%s.json' local BACKUP_FILE_NAME = 'tmp-backup' local BACKUP_FILE_PATH = FILE_PATH_PATTERN:format(BACKUP_FILE_NAME) @@ -190,6 +190,7 @@ function test.import_export_reaction_condition() } ], "job" : "CustomReaction", + "name" : "Make soap from tallow", "reaction" : "MAKE_SOAP_FROM_TALLOW" } ]